I have a Vue component that calls a Vuex action in its create hook (an api.get that fetches some data, and then dispatches a mutation). After that mutation is completed, I need to call an action in a different store, depending on what has been set in my store's state... in this case, getUserSpecials.
I tried to use .then() on my action, but that mutation had not yet completed, even though the api.get Promise had resolved, so the store state I needed to check was not yet available.
Does anyone know if there is a 'best practice' for doing this? I also considered using a watcher on the store state.
In my component, I have:
created () {
this.getUserModules();
if (this.userModules.promos.find((p) => p.type === 'specials')) {
this.getUserSpecials();
}
},
methods: {
...mapActions('userProfile', ['getUserModules',],),
...mapActions('userPromos', ['getUserSpecials',],),
},
In my store I have:
const actions = {
getUserModules ({ commit, dispatch, }) {
api.get(/user/modules).then((response) => {
commit('setUserModules', response);
});
},
};
export const mutations = {
setUserModules (state, response) {
Object.assign(state, response);
},
};
Right now, the simple if check in my create hook works fine, but I'm wondering if there is a more elegant way to do this.
[1] Your action should return a promise
getUserModules ({ commit, dispatch, }) {
return api.get(/user/modules).then((response) => {
commit('setUserModules', response);
})
}
[2] Call another dispatch when the first one has been resolved
created () {
this.getUserModules().then(response => {
this.getUserSpecials()
})
}
Make your action return a promise:
Change:
getUserModules ({ commit, dispatch, }) {
api.get(/user/modules).then((response) => {
commit('setUserModules', response);
});
},
To:
getUserModules({commit, dispatch}) {
return new Promise((resolve, reject) => {
api.get(/user/modules).then((response) => {
commit('setUserModules', response);
resolve(response)
}).catch((error) {
reject(error)
});
});
},
And then your created() hook can be:
created () {
this.getUserModules().then((response) => {
if(response.data.promos.find((p) => p.type === 'specials'))
this.getUserSpecials();
}).catch((error){
//error
});
},
Related
Currently I have defined in a functional component a useEffect as below
useEffect(() => {
(async function () {
posts.current = await BlogConsumer.getBlogPosts();
setLoading(false);
})();
return () => {
BlogConsumer.call_controller.abort();
};
}, []);
where this BlogConsumer is defined as below
class BlogConsumer {
static posts = {};
static call_controller = new AbortController();
static async getBlogPosts() {
await axios
.get('https://nice.api', {
signal: this.call_controller.signal,
})
.then(response => {
// treatment for success
})
.catch(error => {
// treatment for erros
});
return this.posts;
}
}
export default BlogConsumer;
The overral ideia is that in the render of the component I'll be calling a static method from my consumer and will retrieve the necessary data. For the pourpuse of not having memory leaks, I have my callback function in my useEffect that will abort my call whenever I unmount the component, but this is not working. React's message of Warning: Can't perform a React state update on an unmounted component. still appears if I enter the component and leave the screen before the API call is finished. I don't know where I am wrong, so I'd like a little help.
Thanks in advance.
You could just cancel the request on unmount. Like this:
export const fetchData = async (signal) => {
try {
const res = await instance.get("/pages/home", {
signal,
});
return res.data;
} catch (error) {
return Promise.reject(error);
}
};
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => {
controller.abort()
};
}, []);
I have this existing working VueJs code
const actions = {
retrieveStatus({ rootState, commit, dispatch }) {
return Axios
.get('/abc/GetStatus', {
params: {
draftId: rootState.eform.Id
}
})
.then(response => {
commit('SET_STATUS', response.data.statusCode);
return response.data;
})
.catch(err => {
throw new Error('Errors');
})
},
I don't see anywhere it uses dispatch but it exists there.
I have a question about actions and mutations.
I have a backend with a web API with the following routes: getProducts (which returns all products) and addProduct, deleteProduct, updateProduct which returns only success or fail.
In the frontend, in the specific module state, I am interested in keeping a list of all products.
state.js:
export default () => ({
products: [],
});
How wrong is the next approach?
Instead of committing mutations on addProduct (push a new product to the state), updateProduct (edit the state) and deleteProduct (splice the state) actions, I “refresh” my state with what I already have added/updated/deleted in the database, dispatching the getProducts action, which is the only one that commits a mutation.
In this way my state is always up to date with the “one single source of truth” (database data), and I also maintain the state up to date on concurrent tabs/computers/browsers requests.
actions.js:
getProducts({commit}) {
const route = `products/todo`;
return axios.get(route)
.then(response => {
commit('SET_PRODUCTS', response.data);
})
.catch(err => {
//....
})
},
addProduct({dispatch}, data) {
const route = `products/todo`;
return axios.post(route, data)
.then((response) => {
return dispatch('getProducts').then(() => {
//....
})
})
.catch(err => {
//...
})
},
deleteProduct({dispatch}, data) {
const route = `products/todo`;
return axios.delete(route)
.then(() => {
return dispatch('getProducts').then(() => {
//..........
})
})
.catch(err => {
//......
})
},
updateProduct({dispatch}, data) {
const route = `products/todo`;
return axios.put(route, data)
.then(response => {
return dispatch('getProducts').then(() => {
//...
})
})
.catch(err => {
//...
})
},
//mutations.js
export default {
SET_PRODUCTS: (state, data) => {
state.products = data;
},
}
Is this a wrong approach? Do I incorrectly use the state management with VUEX if I choose to write the actions and the mutations like that?
how can i avoid the data blink after update store data?
you can see the effect here:
https://drive.google.com/file/d/178raL6AJiC4bpIOImnaTKh6Yf9GruTCz/view?usp=sharing
component:
[...]
mounted() {
this.getIdeasFromBoard(this.$route.params.board_id);
},
[...]
store:
[...]
const actions = {
getIdeasFromBoard({ commit, dispatch }, board_id) {
apiClient
.get('/ideas/' + board_id)
.then((result) => {
console.log('success');
commit("SET_IDEAS_BOARD", result.data);
})
.catch(error => {
console.log('error' + error);
alert("You have failed to log in. Try again with another credentials.");
dispatch('auth/logout', null, { root: true });
this.$router.push({ name: "public" });
});
},
[...]
i've searched some simple tutorial about consuming api with error handling, but didnt find it.
thanks
It's because IDEAS_BOARD has the previous data until the new API call is completed. You would need to display a loader or a blank screen until the API call for the selected board is completed.
From actions, return a promise so that your component knows when is the call completed.
getIdeasFromBoard({ commit, dispatch }, board_id) {
return new Promise((resolve, reject) => {
apiClient
.get('/ideas/' + board_id)
.then((result) => {
console.log('success');
commit("SET_IDEAS_BOARD", result.data);
resolve()
})
.catch(error => {
console.log('error' + error);
alert("You have failed to log in. Try again with another credentials.");
dispatch('auth/logout', null, { root: true });
this.$router.push({ name: "public" });
reject()
});
})
},
In your .vue component,
async mounted () {
this.loading = true // some flag to display a loader instead of data
await this.$store.dispatch()
this.loading = false
}
There must be some other ways too like having this loading flag in the Vuex store. But it depends on you
I have the following setup for my actions:
get1: ({commit}) => {
//things
this.get2(); //this is my question!
},
get2: ({commit}) => {
//things
},
I want to be able to call one action from within another, so in this example I want to be able to call get2() from within get1(). Is this possible, and if so, how can I do it?
You have access to the dispatch method in the object passed in the first parameter:
get1: ({ commit, dispatch }) => {
dispatch('get2');
},
This is covered in the documentation.
You can access the dispatch method through the first argument (context):
export const actions = {
get({ commit, dispatch }) {
dispatch('action2')
}
}
However, if you use namespaced you need to specify an option:
export const actions = {
get({ commit, dispatch }) {
dispatch('action2', {}, { root: true })
}
}
for actions that does not require payload
actions: {
BEFORE: async (context, payload) => {
},
AFTER: async (context, payload) => {
await context.dispatch('BEFORE');
}
}
for actions that does require payload
actions: {
BEFORE: async (context, payload) => {
},
AFTER: async (context, payload) => {
var payload = {}//prepare payload
await context.dispatch('BEFORE', payload);
}
}
we can pass parameters also while dispatching.
dispatch('fetchContacts', user.uid);
export actions = {
GET_DATA (context) {
// do stuff
context.dispatch('GET_MORE_DATA');
},
GET_MORE_DATA (context) {
// do more stuff
}
}