Should I use async/await in Vuex store actions? - vuex

Is it a good practice to use async/await in Vuex store actions with Nuxt or is it useless and why ?
export const actions = {
async getFoo({ state, commit }) {
await this.$axios.get('api/foo').then((res) => {
commit('FOO_SETUP', res.data)
})
}
}

Yes, use them as you please, that's the whole point of actions (vs mutations, who MUST be synchronous)
You can also await them
await dispatch('getFoo')

Related

Vuex: Difference between using an Action vs. handling within component?

I am confused at why actions in Vuex can't just be handled within a component.
Assuming I have a basic store:
store.js
const initialState = () => ({
overlayText: ''
})
const mutations = {
setOverlayText: (state, payload) => {
state.overlayText = payload;
},
}
const actions = {
clearOverlay: (context, data) => {
return axios.get(data.url).then((response) => {
context.commit('setOverlayText', response);
});
},
}
If I want to make an API call and change data based off it like below using a Vuex Action:
Option 1
<button #click="dispatchClearOverlay">Get Data</button>
methods: {
clearOverlay() {
this.$store.dispatch('clearOverlay', {
url: '/api/clear-overlay',
})
}
}
what is the difference of just doing it within the component like this?
Option 2
<button #click="clearOverlay">Get Data</button>
methods: {
clearOverlay() {
axios.get('api/clear-overlay')
.then(resp => {
this.$store.commit('setOverlayText', response);
})
}
}
The examples you gave are slightly different in that in Option 1, the only possible value that will get stored in state.overlayText is the response from /api/clear-overlay. But in Option 2, you could pass any arbitrary text when you commit the mutation and that value would be stored in state.overlayText.
More generally, there are some important differences. Mutations have to be synchronous and Actions can be asynchronous. You can also fire multiple mutations by dispatching a single action (imagine if you frequently needed to call the same three mutations). These two features can help keep your components nice and lean, while centralizing more of the Store logic.
The Dispatching Actions section of the Actions docs helps illustrate these points.

How to return promise result from Vuex action instead of the promise itself?

I have an action in my vuex store:
export const actions = {
myaction() {
return 'foo'
}
}
Can I get the promise result (here foo) in the mounted() life cycle hook and display it in the console ? If yes, how ?
I tried this:
mounted() {
console.log(
this.$store
.dispatch('myaction')
.then(res => res)
)
}
But it returns the promise instead of the promise result I'm expecting.
Either of these should work:
Using .then():
mounted() {
this.$store
.dispatch('myaction')
.then(res => console.log(res));
}
Or if you're using ES2017 or later (or some compatibility tool like Babel), then you can use async/ await:
async mounted() {
const res = await this.$store.dispatch('myaction');
console.log(res);
}
As #Dan mentions below, whilst this will return you the value of the Promise, this is not the intended usage of Vuex, which prefers all data to be saved to and accessed from Vuex's state.
It would be better to mutate the store in your action to save the value and then use a computed property in your component to retrieve it from state, either directly or through a getter. This can be done very cleanly with the mapGetters Vuex helper function.

Nuxt Fetch Doesn't Update on First Load

I'm having the following issue and hope someone could help me on it:
Fetch is not working on the first load (nor on reloads). It only works when on the client-side (when I move between routes).
I've read that watchQuery could help but didn't understand why and how to use it.
<script>
export default {
async fetch() {
const userId = await this.$nuxt.context.store.state.auth.authUser.userId
await this.$store.dispatch('case/fetchMyCases', userId.uid)
await this.$store.dispatch('case/fetchMyPendingCases', userId.uid)
...
It doesn't work even if I import and use firebase/auth directly.
<script>
import * as firebase from 'firebase/app'
import 'firebase/auth'
export default {
async fetch() {
const userId = await firebase.auth().currentUser
await this.$store.dispatch('case/fetchMyCases', userId.uid)
await this.$store.dispatch('case/fetchMyPendingCases', userId.uid)
...
Does anyone have any tips for it? I'd really appreciate it.
Thanks!
After literally 3 days searching/testing, I finally found out why I was having this issue.
The problem was that I simply put async/await for fetch but didn't put async/await for the actions itself. Therefore, my getter (in computed) was getting the store state before the dispatches have been finished.
Thanks, everyone!
Warning: You don't have access of the component instance through this inside fetch because it is called before initiating the component (server-side).
async fetch({ store }) {
await store.dispatch('case/fetchMyCases')
await store.dispatch('case/fetchMyPendingCases')
}
If you need parameter:
async fetch({ store, params }) {
await store.dispatch('case/fetchMyCases', params.uid)
await store.dispatch('case/fetchMyPendingCases', params.uid)
}
I gave an example of id. The name of the parameter depends on the name of your page.
_id => params.id
_uid => params.uid
_slug => params.slug
...
Yes, You must put async/await on actions.
async automatically returns a promise
If you don't need the value, in this case, don't anything return.
export const Actions = {
async fetchUsers() {
// It will return automatically promise
await this.$axios.get('API')
}
}
// If you need returne value
// First way
export const Actions = {
async fetchUsers() {
// It will return promise and value
return await this.$axios.get('API')
}
}
// Second way
export const Actions = {
async fetchUsers() {
// It will return promise and value
const response = await this.$axios.get('API')
return response;
}
}

Nuxt Vuex mutation runs but doesn't update state

I'm trying to get some data by nuxtServerInit and save it in state
store/index.js
import { fireDb } from '~/plugins/firebase'
export const state = () => ({
posts: []
})
export const mutations = {
addPosts (state, post) {
state.posts.push(post)
console.log('mutation =>', state.posts.length)
}
}
export const actions = {
nuxtServerInit (state, ctx) {
fireDb.collection('posts').orderBy('timestamp', 'desc').limit(3).get().then((snapshot) => {
snapshot.forEach((doc) => {
state.commit('addPosts', doc.data())
})
console.log('action => ', state.posts.length)
})
}
}
when I run this code console output is
mutation => 1
mutation => 2
mutation => 3
ERROR Cannot read property 'length' of undefined
And vue dev tools also doesn't show there's data inside posts[].
What am I missing here?
It looks like nuxtServerInit is dispatched as an action with the Nuxt context. Being an action, the first argument will be the Vuex context.
The Vuex context exposes several properties including state and commit.
The docs also say:
Note: Asynchronous nuxtServerInit actions must return a Promise or leverage async/await to allow the nuxt server to wait on them.
You can change your code to:
async nuxtServerInit({state, commit}, ctx) {
let snapshot = await fireDb.collection('posts').orderBy('timestamp', 'desc').limit(3).get();
snapshot.forEach((doc) => {
commit('addPosts', doc.data())
});
console.log('action => ', state.posts.length)
}

I am getting this error: actions must be plain objects. Use custom middleware for async actions

I'm getting the error:
Actions must be plain objects. Use custom middleware for async actions.
I've tried the solution in the following Stack Overflow question, but it didn't work:
React-Redux: Actions must be plain objects. Use custom middleware for async actions
action
export async function signupp(data){
console.log('In signupp:');
try{
const request = await axios({
method:'POST',
url:'http://192.168.1.10:3003/users/signup',
data:{
email:data.email,
password:data.password
},
}).then(response=>{
console.log(response.data);
return response.data
}).catch( e => {
console.log(e);
return false
});
return {
type:'signup',
payload:request
}
}
catch(e) {
console.log(e);
return false;
}
}
reducer
export default function(state={},action){
switch(action.type){
case 'signup':
return {
...state,
auth:{
email: action.payload.email,
password:action.payload.password
}
}
}
}
store
const createStoreWithMiddleware = applyMiddleware()(createStore);
const appRedux = () => (
<Provider store = {createStoreWithMiddleware(reducers)}>
<App/>
</Provider>
)
AppRegistry.registerComponent(appName, () => appRedux);
BTW, I am getting the right response in the log.
Inside of the component, in the place where you call signupp function, you have mapDispatchToProps function as callback in connect function from react-redux lib, which is doing behind the hoods something like dispatch(signupp())(or maybe you are doing dispatch directly without react-redux lib).
According to redux API, this dispatch function expects to receive a plain object, but your signupp() function returns a promise(as you have async inside).
To solve this problem you can simply use redux-thunk middleware. Also you can see some examples in the redux docs section about async actions.
An alternative solution could be to move fetch logic to component and then dispatch just plain object with data that you received from the request.