How do you maintain multiple loading states in Vuex?
One of the best ways to improve the user experience in a dynamic application is by setting up loading icons for data coming into your Vuex store. But if you’ve ever tried to set this up on a page with a lot of components that are loading different dynamic data. How do you show the user that data is being loaded when that data might be coming from different, parallel API calls?
Your first attempt might be to have a loading
value in your Vuex store that you set to true
when you are loading in data and false
once the data is loaded. But that quickly becomes not good enough once you are loading in multiple calls.
And there are a number of other schemes that just don’t cut it either. Things like incrementing a loading
count or checking to see if data is filled in or not–which I do talk about in How to show a loading icon before data is loaded in Vue and Vuex but is only reliable in simple data loads. All of these have edge cases that pop up and ruin things at some point.
Using a status map
A better way to handle this is to use a status map within Vuex (assuming Vuex is where you are making the API calls, as described in How to query your API using Vuex in your Vue application).
A status map is an object in your Vuex store that you use to keep metadata information about the data that you’ve loaded, the state of it, and any other information that you want to keep track of for it. By having a single source for this information, you’ll have a one stop location for all of your components to get updates on the current status of the data they care about. I’m going to show this with a loading
status for the data, and an error
status.
You can set this up in your Vuex state
with:
state: {
posts: [],
users: [],
status: {
users: {
loading: false,
error: false
},
posts: {
loading: false,
error: false
}
}
},
You can then set statuses in a mutation, just like we would any dynamically loaded data. Here’s what that would look like for the users
state:
mutations: {
SAVE_USERS(state, users) {
state.users = users;
},
SAVE_LOADING(state, dataName, status) {
// create a property in status
state.status[dataName].loading = status;
},
SAVE_ERROR(state, dataName, status) {
// create a property in status
state.status[dataName].error = status;
}
},
actions: {
loadUsers({commit}) {
commit('SAVE_STATUS', 'users', true);
Vue.axios.get('users').then(result => {
commit('SAVE_USERS', result.data);
commit('SAVE_STATUS', 'users', false);
}).catch(error => {
commit('SAVE_ERROR', 'users', true);
commit('SAVE_STATUS', 'users', false);
throw new Error(`API ${error}`);
});
}
}
With these statuses set, we can now use them in our component. If we have a component that uses users
and posts
, for example, we could add this if to the loader:
<div v-if="$store.state.status.users.loading || $store.state.status.posts.loading">
<img src="./../assets/loading icon.png">
</div>
<table class="users" v-else>
<!-- data display once the data is loaded -->
</table>
The above will only show the table once the users
and posts
loading statuses are both false. And you can set up the same sort of logic for showing an error
status as well.
This way of handling statuses are something I found by looking at how Vue Apollo handles loading state and realized that the same thing could be used for Vuex as well. It makes is super easy for a component to watch for the data that it cares about only.
Try this out and see if it doesn’t make handling loading states a lot easier.