Handing Loading Flags in Pinia Stores
We often use Pinia stores to make requests to our backend APIs through actions. We often set a ‘loading’ flag that can be easily bound to some frontend UI letting the user know they’re waiting on a request. But what happens when your store is making multiple requests? Well, I have a simple design pattern that will help with that.
Imagine we have the following store.
export const useFileSystemStore = defineStore('filesystem', {
state: () => ({
files: {} as FileList,
fileTypes: {} as FileTypeList,
folder: {} as Folder,
folders: {} as FolderList,
loading: false as boolean,
error: '' as string,
}),
getters: {},
actions: {
async getFolders() {
this.loading = true;
await api.get('/api/folders')
.then((response) => {
this.loading = false;
...
})
.catch((error) => {
...
});
},
async getFolder(folderID:string) {
this.loading = true;
await api.get(`/api/folders${folderID}`)
.then((response) => {
this.loading = false;
...
})
.catch((error) => {
...
});
},
async getFileTypes() {
this.loading = true;
await api.get('/api/filetypes')
.then((response) => {
this.loading = false;
...
})
.catch((error) => {
...
});
},
async getFiles(folderID: string) {
this.loading = true;
await api.get(`/api/files${folderID}`)
.then((response) => {
this.loading = false;
...
})
.catch((error) => {
...
});
},
},
});
Our store has a singular purpose of fetching ‘filesystem’ related data; file, folders, etc. Whenever a call to the API is made, we set the ‘loading’ flag to true. On our frontend we can bind something, say a loading spinner, to that flag and the user knows things are happening.
The Problem
The problem with this store is if multiple actions are made at the same time, results from one action will cancel the loading flag for other actions before their results have finished.
Solution One
One way we can handle this is to just make our action calls one at a time.
const fileSystemStore = useFileSystemStore();
fileSystemStore.getFolder(folderID).then(() => {
fileSystemStore.getFiles(folderID);
});
This ensures our loading flag is accurate. However, this is slow. Modern browsers can handle six requests at once to the same host. Wouldn’t it be getter if we took advantage of that?
Solution Two (Better)
Another way to handle this is to use an Array as your loading flag.
export const useFileSystemStore = defineStore('filesystem', {
state: () => ({
files: {} as FileList,
fileTypes: {} as FileTypeList,
folder: {} as Folder,
folders: {} as FolderList,
loadArr: [] as Array<string>,
error: '' as string,
}),
getters: {
loading: (state) => state.loadArray.length > 0,
},
actions: {
async getFolders() {
this.loadArr.push('getFolders');
await api.get('/api/folders')
.then((response) => {
this.loadArray.splice(
this.loadArray.indexOf('getFolders'),
1
);
...
})
.catch((error) => {
...
});
},
async getFolder(folderID:string) {
this.loadArr.push('getFolder');
await api.get(`/api/folders${folderID}`)
.then((response) => {
this.loadArray.splice(
this.loadArray.indexOf('getFolder'),
1
);
...
})
.catch((error) => {
...
});
},
async getFileTypes() {
this.loadArr.push('getFileTypes');
await api.get('/api/filetypes')
.then((response) => {
this.loadArray.splice(
this.loadArray.indexOf('getFileTypes'),
1
);
...
})
.catch((error) => {
...
});
},
async getFiles(folderID: string) {
this.loadArr.push('getFiles');
await api.get(`/api/files${folderID}`)
.then((response) => {
this.loadArray.splice(
this.loadArray.indexOf('getFiles'),
1
);
...
})
.catch((error) => {
...
});
},
},
});
Now, we can make as many action calls as we want at the same time. Anything that was watching the ‘loading’ flag will still work because the ‘loading’ getter we added will stay true until all requests are complete.
When binding to the loading flag on our frontend, we have choices. We can bind to the ‘loading’ getter flag, or bind to our ‘loadArr’ for a specific action!
<template v-if="fileSystemStore.loading">
Loading...
</template>
...
<template v-if="fileSystemStore.loadArr.includes('getFiles')">
Loading Files...
</template>
Conclusion
The load array pattern offers a simple approach to handling loading flags where multiple action requests are happening at once. It makes binding in templates easy and straightforward, and allows you to leverage the browsers ability to manage multiple simultaneous requests.