Example of managing location and language settings in Vue3 using Pinia
Mar 13, 2024
Viktor Lövgren
Background
In this post we explored how to persist user settings for location and language using only localstorage and listeners to listen for changes of the settings. This is an overly verbose method which soon becomes difficult to maintain. A much smoother way is to utilize a state management tool, such as Pinia. Pinia lets you set up and manage stores easily, and you won’t need any explicit event listeners set up. Below we will take a look at how we implemented Pinia for the location and language states on Vegan Monkey.
1. Install Pinia and set up the root store
Start by installing Pinia in your project. In the terminal:
(Adapt to your package manager)
(For us, Vue devtools only picked up Pinia when we created the root store inside of app.use)
Now you’re ready to start using stores in your app.
2. Set up your first store
We keep the stores in a folder (“stores”) in src. One .js file per store. For our case, we create three stores and three files city.js, country.js, language.js. From each file we export the store:
Note that it might be a better idea to save everything to localstore to avoid re-fetch on next session. As long as you have a way to bust the localstorage when you know you have new data you want the user to get on next visit (see BONUS section at the bottom for a way to do this).
The city and language stores are dependent on the country store. If country changes, city and language needs to adapt. For this, we use the country store in the city and language stores, and react to the changes that happen. For an example of this, let’s look at how the city changes when country changes.
3. Use a store in another store (composing stores)
Time to set up the city store. In addition to the same imports as in the country store, we also need to import the country store and a Pinia composable function called storeToRefs when we create the city store:
storeToRefs allow you to destructure a store while keeping the refs reactive. This is needed if you want to watch the ref or use it in a computed ref (when using the store refs in the template you can use them directly and they will still be reactive, no need for storeToRefs).
Then we create the store in the same way as for the country store:
To destructure the store while keeping reactivity of the ref:
(In this case, we destructure for selectedCountry which is a ref in the contry store)
Now we can watch for changes in the selectedCountry property of the countryStore:
(Remember to import watch from vue as well)
Here is the full code for the city store:
4. Using the stores
You can now use the stores in any components that need this data. The initialization of the stores works the same as in the city store above where we used the country store. For example, in our navbar we use all three stores, so we import and initialize them in our script setup:
Now you can use the store in your template. It will be reactive if you refer to it in the template:
(Example of the list for our dropdown button to select city)
Note that if you need the ref to be reactive for use in the script setup (to watch, or use in a computed prop) you need to destructure it as described above.
5. Comparison to old method
The ease of managing everything relating to the store in one file, and then just import and use the properties wherever, makes it much more scalable than the previous method where you have to remember to set up event listeners for any change. With Pinia installed you can also simplify other parts of the app where you need data in several places, or want it stored in the browser when you navigate away from a page etc.
It’s a good idea to have a reliable way to be able to clear localstorage for users on next visit when you have made code changes that might break with old data stored in localstorage. With this small snippet in your App.vue or other entrypoint you can achieve that:
When you make breaking changes, update the versioning and on next visit, the user’s browser will clear localstorage.