Nuxt.js Vuex / The console.log in mutation can see the future state? - vuex

I am using Nuxt.js and module Vuex to build my app, but I have a question
I have a button which can trigger test() function
page/login.vue
<button #click="test">Test</button>
methods: {
test() {
this.$store.commit('user/test', { value: 'after' })
}
}
store/user.js
export const state = () => ({
hello: 'before'
})
export const mutations = {
test(state, { value }) {
console.log(state) // <- when I trying to console.log here, the value of hello was 'after'
state.hello = value
}
}
Did I miss something or that's normal?

Related

Vuex + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

How to commit mutation from `plugins` directory using Nuxt.js?

I have just run into such a problem, I am trying to customize Axios module, My aim is to access my dom.js vuex module state from 'plugins' directory, The code below works but I have the following error in the console
Do not mutate vuex store state outside mutation handlers
So, The reason for this error is also clear to me, I wonder how I can Commit mutation from 'plugins' directory to my dom.js vuex module?
Thanks!
//plugins/axios.js
export default function ({ $axios, redirect, store}) {
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
store.state.dom.alertIs = true
redirect('/')
}
})
}
/store/dom.js
export const state = () => ({
alertIs:false
})
Declare a mutation (named "SET_DOM_ALERT") in your store:
// store/dom.js
export default {
state: () => ({
alertIs: false
}),
mutations: {
SET_DOM_ALERT(state, value) {
state.alertIs = value
}
}
}
Then, use store.commit('dom/SET_DOM_ALERT', newValue) in your plugin (notice the dom/ prefix for the namespace):
// plugins/axios.js
export default function ({ $axios, redirect, store}) {
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
store.commit('dom/SET_DOM_ALERT', true) // 👈
redirect('/')
}
})
}
demo

How to properly use Vuex getters in Nuxt Vue Composition API?

I use #nuxtjs/composition-api(0.15.1), but I faced some problems about accessing Vuex getters in computed().
This is my code in composition API:
import { computed, useContext, useFetch, reactive } from '#nuxtjs/composition-api';
setup() {
const { store } = useContext();
const products = computed(() => {
return store.getters['products/pageProducts'];
});
const pagination = computed(() => {
return store.getters['products/pagination'];
});
useFetch(() => {
if (!process.server) {
store.dispatch('products/getPage');
}
});
return {
products,
pagination,
};
}
And the console keeps reporting the warning:
[Vue warn]: Write operation failed: computed value is readonly.
found in
---> <Pages/products/Cat.vue> at pages/products/_cat.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
I'm really confused. Because I didn't try to mutate the computed property, just fetching the Data with the AJAX and then simply assign the data to the state in the Vuex mutations.
But I rewrite the code in option API in this way:
export default {
components: {
ProductCard,
Pagination,
},
async fetch() {
if (process.server) {
await this.$store.dispatch('products/getPage');
}
},
computed: {
products() {
return this.$store.getters['products/pageProducts'];
},
pagination() {
return this.$store.getters['products/pagination'];
},
},
};
Everything works fine, there's no any errors or warnings. Is it the way I'm wrongly accessing the getters in the composition API or that's just a bug with the #nuxtjs/composition-api plugin?
fix: computed property hydration doesn't work with useFetch #207
This problem might not can be solved until the Nuxt3 come out.
But I found an alternative solution which use the middleware() instead of use useFetch(), if you want to the prevent this bug by fetching AJAX data with Vuex Actions and then retrieve it by Getters via the computed().
I make another clearer example which it's the same context like the question above.
~/pages/index.vue :
<script>
import { computed, onMounted, useContext, useFetch } from '#nuxtjs/composition-api';
export default {
async middleware({ store }) {
await store.dispatch('getUser');
},
setup() {
const { store } = useContext();
const user = computed(() => store.getters.user);
return {
user,
};
},
}
</script>
~/store/index.js (Vuex)
const state = () => ({
user: {},
});
const actions = {
async getUser({ commit }) {
const { data } = await this.$axios.get('https://randomuser.me/api/');
console.log(data.results[0]);
commit('SET_USER', data.results[0]);
},
};
const mutations = {
SET_USER(state, user) {
state.user = user;
},
};
const getters = {
user(state) {
return state.user;
},
};
If there's something wrong in my answer, please feel free to give your comments.

from same axios to different components, VueJS

Okay, I have two different components and each of those get Axios response. But I don't want to fetch data in each component separate. That's is not right, and it cause components run separate...
Updated 3
I did some changes on the code, but still having some problems. I am doing axios call with Vuex in Store.js and import it into my component. it's like below.
This is my store.js component;
import Vue from "vue";
import Vuex from "vuex";
var actions = _buildActions();
var modules = {};
var mutations = _buildMutations();
const state = {
storedData: []
};
Vue.use(Vuex);
const getters = {
storedData: function(state) {
return state.storedData;
}
};
function _buildActions() {
return {
fetchData({ commit }) {
axios
.get("/ajax")
.then(response => {
commit("SET_DATA", response.data);
})
.catch(error => {
commit("SET_ERROR", error);
});
}
};
}
function _buildMutations() {
return {
SET_DATA(state, payload) {
console.log("payload", payload);
const postData = payload.filter(post => post.userId == 1);
state.storedData = postData;
}
};
}
export default new Vuex.Store({
actions: actions,
modules: modules,
mutations: mutations,
state: state,
getters
});
Now importing it into Average component.
import store from './Store.js';
export default {
name:'average',
data(){
return{
avg:"",
storedData: [],
}
},
mounted () {
console.log(this.$store)
this.$store.dispatch('fetchDatas')
this.storedData = this.$store.dispatch('fetchData')
},
methods: {
avgArray: function (region) {
const sum = arr => arr.reduce((a,c) => (a += c),0);
const avg = arr => sum(arr) / arr.length;
return avg(region);
},
},
computed: {
mapGetters(["storedData"])
groupedPricesByRegion () {
return this.storedData.reduce((acc, obj) => {
var key = obj.region;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj.m2_price);
return acc;
}, {});
},
averagesByRegion () {
let arr = [];
Object.entries(this.groupedPricesByRegion)
.forEach(([key, value]) => {
arr.push({ [key]: Math.round(this.avgArray(value)) });
});
return arr;
},
}
}
I can see the data stored in the console. But there are errors too. I can't properly pass the data in myComponent
https://i.stack.imgur.com/J6mlV.png
if you don't want use vuex to distrib data maybe you can try eventBus, when you get data form the axios respose #emit the event and in another component #on this event
The issue is that
To resolve the error you're getting, Below are the steps.
Import your store file inside the file where your Vue Instance is initialized.
// Assuming your store file is at the same level
import store from './store';
Inside your, Add store object inside your Vue Instance
function initApp(appNode) {
new Vue({
el: appNode,
router: Router,
store // ES6 sytax
});
}
There you go, you can now access your store from any component.
UPDATE: For Second Error
Instead of changing data inside your component, change it inside mutation in the store because you do not want to write the same login in other components where the same method is used.
Hence,
computed: {
...mapGetters(["storedData"]),
anotherFunction() {
// Function implementation.
}
}
Inside your mutation set the data.
SET_DATA(state, payload) {
console.log("payload", payload);
state.storedData = payload;
}
Inside getters, you can perform what you were performing inside your computed properties.
storedData: function(state) {
const postData = state.storedData.filter(post => post.userId == 1);
return postData;
}
Vuex Official docs
Here is the working codesandbox
Hope this helps!

How can I get response ajax by vuex store in the vue component?

My component vue like this :
<template>
...
</template>
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
...
methods: {
add(event) {
this.addProduct(this.filters)
console.log(this.getStatusAddProduct)
if(this.getStatusAddProduct) {
...
}
},
...mapActions(['addProduct'])
},
computed: {
...mapGetters(['getStatusAddProduct'])
}
}
</script>
This code : this.addProduct(this.filters), it will call addProduct method n the modules vuex
My modules vuex like this :
import { set } from 'vue'
import product from '../../api/product'
import * as types from '../mutation-types'
// initial state
const state = {
statusAddProduct: null
}
// getters
const getters = {
getStatusAddProduct: state => state.statusAddProduct
}
// actions
const actions = {
addProduct ({ dispatch, commit, state }, payload)
{
product.add(payload,
data => {
commit(types.ADD_PRODUCT_SUCCESS)
},
errors => {
commit(types.ADD_PRODUCT_FAILURE)
}
}
}
}
// mutations
const mutations = {
[types.ADD_PRODUCT_SUCCESS] (state){
state.statusAddProduct = true
},
[types.ADD_PRODUCT_FAILURE] (state){
state.statusAddProduct = false
}
}
export default {
state,
getters,
actions,
mutations
}
This code : product.add(payload, in the modules vuex, it will call api
The api like this :
import Vue from 'vue'
export default {
add (filter, cb, ecb = null ) {
axios.post('/member/product/store', filter)
.then(response => cb(response))
.catch(error => ecb(error))
}
}
My problem here is if add method in vue component run, the result of console.log(this.getStatusAddProduct) is null. Should if product success added, the result of console.log(this.getStatusAddProduct) is true
I think this happens because at the time of run console.log(this.getStatusAddProduct), the process of add product in vuex modules not yet finished. So the result is null
How can I make console.log(this.getStatusAddProduct) run when the process in the vuex module has been completed?
You have to pass the property down all the way from the .add() method.
You do that by returning it in the intermediary methods and, lastly, using .then().
The api:
add (filter, cb, ecb = null ) {
return axios.post('/member/product/store', filter) // added return
.then(response => cb(response))
.catch(error => ecb(error))
}
And, the action:
addProduct ({ dispatch, commit, state }, payload) // added return
{
return product.add(payload,
data => {
commit(types.ADD_PRODUCT_SUCCESS)
},
errors => {
commit(types.ADD_PRODUCT_FAILURE)
}
}
}
Finally:
methods: {
add(event) {
this.addProduct(this.filters).then(() => { // added then
console.log(this.getStatusAddProduct) // moved inside then
if(this.getStatusAddProduct) {
...
}
})
},