My data variable not reflect to components props in vue - vue.js

I have this code
<template>
<AppLayout :user="user">
<router-view :user="user" />
</AppLayout>
</template>
<script setup>
import LoginService from '#/services/LoginService';
import { inject, onMounted } from 'vue';
let user = null;
const $cookies = inject('$cookies');
async function getFuncionario() {
const publicToken = $cookies.get('PublicToken');
console.log(publicToken);
if (publicToken) {
await LoginService.getFuncionarioForMenu(publicToken)
.then((res) => {
user = res.data;
})
.catch((error) => {
console.log(error);
// redirect pagina de erro
});
console.log(user);
}
}
onMounted(() => {
getFuncionario();
});
</script>
I passed user variable like a props for my components
This "user" variable isn't updated after set my data to API:
user = res.data;
This variable does not reflect in my component
My component
<script>
import { markRaw } from 'vue';
const emptyLayout = 'EmptyLayout';
export default {
name: 'AppLayout',
data: () => ({
layout: emptyLayout,
}),
props: {
user: null,
},
watch: {
$route: {
immediate: true,
async handler(route) {
try {
const component = await import(`#/layouts/${route.meta.layout}.vue`);
this.layout = markRaw(component?.default || emptyLayout);
} catch (e) {
this.layout = emptyLayout;
}
},
},
},
};
</script>
Any ideas

I found the answer
I change my variable to
const user = ref(null);
this ref make a reflect in lifecycle
and I set the variable like this
user.value = res.data;

Related

How to use Pinia with Nuxt, composition-api (vue2) and SSR?

I'm trying to get Pinia to work in Nuxt with SSR (server-side rendering).
When creating a page without Pinia, it works:
<script>
import { reactive, useFetch, useContext } from '#nuxtjs/composition-api'
export default {
setup() {
const { $axios } = useContext()
const invitesStore = reactive({
invites: [],
loading: true,
})
useFetch(async () => {
invitesStore.loading = true
await $axios.$get('invite/registermember').then((result) => {
invitesStore.loading = false
invitesStore.invites = result.invites
})
})
return {
invitesStore,
}
},
}
</script>
But when introducing Pinia, I get the error "Converting circular structure to JSON --> starting at object with constructor 'VueRouter'"
I'm using Pinia this way:
// /store/invitesStore.js
import { defineStore } from 'pinia'
// useStore could be anything like useUser, useCart
export const useInvitesStore = defineStore({
// unique id of the store across your application
id: 'storeId',
state() {
return {
invites: [],
loading: true,
}
},
})
<script>
import { useInvitesStore } from '#/store/invitesStore'
import { reactive, onMounted, useFetch, useContext } from '#nuxtjs/composition-api'
export default {
setup() {
const { $axios } = useContext()
const invitesStore = useInvitesStore()
useFetch(async () => {
invitesStore.loading = true
await $axios.$get('invite/registermember').then((result) => {
invitesStore.loading = false
invitesStore.invites = result.invites
})
})
return {
invitesStore,
}
},
}
</script>
Is it possible to get this to work? How?

How to call a namespaced Vuex action in Nuxt

I am building an app using NUXT js. I am also using the store module mode because using classic mode returned some depreciation issue.
The PROBLEM is I get [vuex] unknown mutation type: mobilenav/showmobilenav error in my console.
so below are my stores
store/index.js
export const state = () => ({
})
export const mutations = ({
})
export const actions = ({
})
export const getters = ({
})
store/mobilenav.js
export const state = () => ({
mobilenav: false
})
export const mutations = () => ({
showmobilenav(state) {
state.mobilenav = true;
},
hidemobilenav(state) {
state.mobilenav = false;
}
})
export const getters = () => ({
ismobilenavvisible(state) {
return state.dropdown;
}
})
the VUE file that calls the mutation
<template>
<div class="bb" #click="showsidenav">
<img src="~/assets/svg/burgerbar.svg" alt="" />
</div>
</template>
<script>
export default {
methods: {
showsidenav() {
this.$store.commit("mobilenav/showmobilenav");
console.log("sidenav shown");
},
},
}
</script>
<style scoped>
</style>
Here is a more detailed example on how to write it.
/store/modules/custom_module.js
const state = () => ({
test: 'default test'
})
const mutations = {
SET_TEST: (state, newName) => {
state.test = newName
},
}
const actions = {
actionSetTest({ commit }, newName) {
commit('SET_TEST', newName)
},
}
export const myCustomModule = {
namespaced: true,
state,
mutations,
actions,
}
/store/index.js
import { myCustomModule } from './modules/custom_module'
export default {
modules: {
'custom': myCustomModule,
},
}
/pages/test.vue
<template>
<div>
<button #click="actionSetTest('updated test')">Test the vuex action</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions('custom', ['actionSetTest']),
}
</script>

Vuex action not works with namespaced inside Nuxt fetch

I am building my own small project. When i try to access states from main store (index.js) inside of nuxt fetch method all works fine, but while i am trying to access from namespaced(store/photos.js) store it wont work. Here is my code.
store/index.js ( Works )
export const state = () => ({
fetchedData: []
})
export const mutations = {
setData: (state, data) => {
state.fetchedData = data;
}
}
export const actions = {
async get(vuexContext) {
const requestedData = await this.$axios.get("https://jsonplaceholder.typicode.com/users");
vuexContext.commit('setData', requestedData.data);
},
}
my Component:
<script>
import { mapState, mapActions } from 'vuex'
export default {
async fetch({ error,store })
{
try {
await store.dispatch('get');
} catch (error) {
console.log(error);
}
},
computed: {
...mapState(['fetchedData'])
}
};
</script>
store/photos.js ( Does not works )
export const state = () => ({
list: []
});
export const mutations = {
setPhotos(state, data) {
state.list = data;
}
};
export const actions = {
async getPhotos(vuexContext, context) {
const requestedData = await this.$axios.get(
"https://jsonplaceholder.typicode.com/photos"
);
vuexContext.commit("setPhotos", requestedData.data);
}
};
Same Component but modified
<script>
import { mapState, mapActions } from 'vuex'
export default {
async fetch({ error,store })
{
try {
await store.dispatch('photos/getPhotos');
} catch (error) {
console.log(error);
}
},
computed: {
...mapState({
list : 'photos/list'
})
}
};
</script>
Thanks in advance.
namespaced: true,
You can add this to your index.js file. Hope this will work.
Reference link:
Try this

Vue Test Utils mount in multiple tests

I am testing my Vue App using Vue Test Utils and Jest. Below is my dashboard component.
<template>
<div class="dashboard-v2">
<div class="component-container">
<component :loading="loading" :key="identifier" :is="currentTab" />
</div>
<SnackBar
v-on:snackBarHide="displaySnackBar = false"
:text="snackBarText"
:show="displaySnackBar"
:type="snackBarType"
/>
</div>
</template>
<script>
import { mapState } from "vuex";
import "#/shared/chart-kick";
import EventBus from "#/shared/event-bus";
import Tabs from "./helpers/Tabs";
import Summary from "./Summary/Index";
import { filters } from "../helpers/filters-details";
import SnackBar from "#/shared/components/SnackBar.vue";
export default {
components: {
Tabs,
Summary,
SnackBar
},
data() {
return {
identifier: +new Date(),
loading: false,
filtersLoading: false,
displaySnackBar: false,
snackBarText: "",
snackBarType: ""
};
},
mounted() {
if (!this.projects.length) this.fetchFilterData();
EventBus.$on("CLEAR_ALL", () => {
this.identifier = +new Date();
this.$store.commit(`dashboardV2/UPDATE_FILTER_STATE`, {});
});
EventBus.$on("filterChange", () => {
this.getExecData();
});
},
computed: {
...mapState("dashboardV2", [
"projects",
"currentTab",
"selectedFilters",
"timeFilter"
])
},
methods: {
fetchFilterData() {
this.filtersLoading = true;
this.$store
.dispatch("dashboardV2/GET_EXEC_FILTER_DATA")
.catch(() => {
this.displaySnackBar = true;
this.snackBarText = "There was some problem while fetching data";
this.snackBarType = "failure";
})
.finally(() => {
this.filtersLoading = false;
});
this.getExecData();
},
getExecData() {
this.loading = true;
let params = {
time_bucket: this.timeFilter,
time_zone_offset: new Date().getTimezoneOffset()
};
filters.map(e => {
params[e.query] = this.selectedFilters[e.value]
? this.selectedFilters[e.value].id
: null;
});
this.$store
.dispatch("dashboardV2/GET_EXEC_DATA", params)
.catch(() => {
this.displaySnackBar = true;
this.snackBarText = "There was some problem while fetching data";
this.snackBarType = "failure";
})
.finally(() => (this.loading = false));
}
}
};
</script>
<style lang="scss" scoped>
#import "#/styles/dashboard.scss";
</style>
Then this is my test file
import Main from "../Main.vue";
import mergeWith from "lodash.mergewith";
import { customizer, createWrapper } from "#/shared/test-helper";
import Vuex from "vuex";
import EventBus from "#/shared/event-bus";
let GET_EXEC_DATA = jest.fn(() => Promise.resolve());
let GET_EXEC_FILTER_DATA = jest.fn(() => Promise.resolve());
export const createStore = (overrides) => {
let storeOptions = {
modules: {
dashboardV2: {
namespaced: true,
state: {
projects: [],
currentTab: "",
selectedFilters: {},
timeFilter: "",
},
actions: {
GET_EXEC_DATA,
GET_EXEC_FILTER_DATA,
},
},
},
};
return new Vuex.Store(mergeWith(storeOptions, overrides, customizer));
};
describe("Loads Main Dashboard", () => {
it("should fetch chart data and filter data", () => {
createWrapper({}, Main, createStore());
expect.assertions(2);
expect(GET_EXEC_DATA).toBeCalled();
expect(GET_EXEC_FILTER_DATA).toBeCalled();
});
it("should call fetch chart data when filter changed", () => {
createWrapper({}, Main, createStore());
EventBus.$emit("filterChange");
expect.assertions(1);
expect(GET_EXEC_DATA).toBeCalledTimes(2);
});
});
My first test is running successfully but my second test is failing because GET_EXEC_DATA is being called 4 times instead of 2 times. Is it because it's being called once in the first test. Then, How do I avoid this?
Actually, I was able to solve this by clearing the mock functions
afterEach(() => {
jest.clearAllMocks();
});

How to use router in vue composition api?

I defined a route in vue:
/users/:userId
Which point to UserComponent:
<template>
<div>{{username}}</div>
</template>
and I use computed from #vue/composition-api to get the data.
the problem is when the route change to another userId, by navigate to another user, the user in the html template not changed as what I expected. also it doesn't do redirect when the the user is not in the list.
So what I can do to fix that?
here is my code:
<template>
<div>{{username}}</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, getCurrentInstance } from '#vue/composition-api';
export const useUsername = ({ user }) => {
return { username: user.name };
};
export default defineComponent({
setup(props, { root }) {
const vm = getCurrentInstance();
const userToLoad = computed(() => root.$route.params.userId);
const listOfUsers = [
{ userId: 1, name: 'user1' },
{ userId: 2, name: 'user2' },
];
const user = listOfUsers.find((u) => u.userId === +userToLoad.value);
if (!user) {
return root.$router.push('/404');
}
const { username } = useUsername({ user });
return { username };
},
});
</script>
You can just do this:
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
// Now you can access params like:
console.log(route.params.id);
}
};
From the vue-router documentation:
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
if (!user) {
router.push({
name: '404',
query: {
...route.query
}
})
}
}
}
}
You can pass the parameters as props to your components. Props are reactive by default.
This is how the route configuration could look like:
{
path: '/users/:userId',
name: Users,
component: YourComponent
},
You can then use the props in your component with watchEffect()
<template>
<div>{{username}}</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, getCurrentInstance, watchEffect } from '#vue/composition-api';
export const useUsername = ({ user }) => {
return { username: user.name };
};
export default defineComponent({
props: {userId: {type: String, required: true },
setup(props, { root }) {
const vm = getCurrentInstance();
const user = ref()
const userToLoad = computed(() => props.userId);
const listOfUsers = [
{ userId: 1, name: 'user1' },
{ userId: 2, name: 'user2' },
];
watchEffect(() => user.value = listOfUsers.find((u) => u.userId === +userToLoad.value))
if (!user) {
return root.$router.push('/404');
}
const { username } = useUsername({ user });
return { username };
},
});
</script>
watchEffect() will run immediately when defined and when reactive dependencies.change
A had the same problem. I use vue 2 and #vue/composition-api
My resolution:
Created: src/router/migrateRouterVue3.js
import { reactive } from '#vue/composition-api';
import router from './index';
const currentRoute = reactive({
...router.currentRoute,
});
router.beforeEach((to, from, next) => {
Object.keys(to).forEach(key => {
currentRoute[key] = to[key];
});
next();
});
// eslint-disable-next-line import/prefer-default-export
export function useRoute() {
return currentRoute;
}
after that, I can usage:
// import { useRoute } from 'vue-router';
import { useRoute } from '#/router/migrateRouterVue3';
Resolution for you:
// replace:
// const userToLoad = computed(() => root.$route.params.userId);
// to:
import { useRoute } from '#/router/migrateRouterVue3';
//...
const route = useRoute();
const userToLoad = computed(() => route.params.userId);
function useRoute() {
const vm = getCurrentInstance()
if (!vm) throw new Error('must be called in setup')
return vm.proxy.$route
}
https://github.com/vuejs/composition-api/issues/630
The following useRoute hook will make route reactive so that it's doable:
const route = useRoute();
const fooId = computed(()=>route.params.fooId);
let currentRoute = null;
export const useRoute = () => {
const self = getCurrentInstance();
const router = self.proxy.$router;
if (!currentRoute) {
const route = { ...self.proxy.$route };
const routeRef = shallowRef(route);
const computedRoute = {};
for (const key of Object.keys(routeRef.value)) {
computedRoute[key] = computed(() => routeRef.value[key]);
}
router.afterEach((to) => {
routeRef.value = to;
});
currentRoute = reactive(computedRoute);
}
return currentRoute;
};
The vue2-helpers package provides a useRoute function you can use in Vue 2.7 (and 2.6, 2.5 also).
Installation
# Vue 2.7
$ npm install vue2-helpers#2
# Vue 2.5 and 2.6
$ npm install vue2-helpers#1
Usage
import { useRoute } from 'vue2-helpers/vue-router';
const route = useRoute();
const id: string | undefined = route.params.id;
const { proxy } = getCurrentInstance();
then use proxy to access $router or $route
Add please this code: watchEffect(() => userToLoad);