Why is my store cleared when I reload the page?
The data in a store, such as Pinia, is only held in memory. There isn't any built-in persistence.
The role of a store is to provide a central place to hold global or application-wide state, rather than keeping it in a specific component. Persisting that state is a separate concern.
But they are often related concerns. If you're persisting data then it typically is 'global', at least in some sense.
We'll discuss various approaches below for persisting data, with or without a store.
Types of persistence
The first thing to consider is where you want to persist the data. The main options are:
- On the server, e.g. in a database. This allows the data to be shared between devices or users.
- Cookies. While cookies are stored in the client, they are automatically sent as headers on HTTP requests, allowing the server to see the persisted data when handling requests.
- Client-side storage, e.g. via the Web Storage API (
localStorage
/sessionStorage
). These won't automatically be included in HTTP requests and have much more generous size limits than cookies. If you're using SSR then you may need to consider how you'll handle rendering on the server, as it won't have access to the persisted data in the user's browser.
Another alternative to consider is 'persisting' the data in the URL. This has other ramifications, such as impacting the browser Back button, but if the requirement is for state to be retained across a browser reload then maybe it should be stored in the page URL? It's usually fairly clear whether this approach is a good fit or not.
For the remainder of this page we're going to focus on client-side persistence, e.g. using cookies, localStorage
or sessionStorage
.
Pinia plugins
There are various Pinia plugins that will handle persistence automatically. The most commonly used is:
It uses localStorage
by default, or cookies if used with Nuxt (for SSR). It also supports sessionStorage
.
You can find several similar libraries in the npm registry.
Doing it yourself
If you only want to persist a small amount of data then it may be possible to do it yourself, without needing any extra libraries. For example:
import { ref, watch } from 'vue'
import { defineStore } from 'pinia'
export const useModeStore = defineStore('mode', () => {
const mode = ref(sessionStorage.getItem('mode') === 'dark' ? 'dark' : 'light')
watch(mode, (value) => sessionStorage.setItem('mode', value))
function toggleMode() {
mode.value = mode.value === 'dark' ? 'light' : 'dark'
}
return { mode, toggleMode }
})
A similar approach can be used to persist a ref, without using Pinia at all:
import { ref, watch } from 'vue'
export const mode = ref(sessionStorage.getItem('mode') === 'dark' ? 'dark' : 'light')
watch(mode, (value) => sessionStorage.setItem('mode', value))
export function toggleMode() {
mode.value = mode.value === 'dark' ? 'light' : 'dark'
}
A few notes on these examples:
- Neither example will work with SSR as they're using
sessionStorage
, which won't be available on the server. - Instead of using
watch
, the call tosetItem
could be made insidetoggleMode
. With that approach, it may also be beneficial to make the exposed ref readonly, so it can only be modified viatoggleMode
. sessionStorage
only accepts string values, which is fine in this example, but may need more work if your data isn't already a string.
VueUse
While VueUse doesn't have a specific persistence plugin for Pinia, it does provide some reactive helpers for working with localStorage
and sessionStore
. Much like in the previous examples, these can be used with or without Pinia:
import { defineStore } from 'pinia'
import { useSessionStorage } from '@vueuse/core'
export const useModeStore = defineStore('mode', () => {
const mode = useSessionStorage('mode', 'dark')
function toggleMode() {
mode.value = mode.value === 'dark' ? 'light' : 'dark'
}
return { mode, toggleMode }
})
The VueUse helpers support various options. For example, initOnMounted: true
can be used when working with SSR. It defers loading the persisted value until after components are mounted, allowing client-side hydration to match the server-generated HTML. e.g.:
const mode = useSessionStorage('mode', 'dark', {
initOnMounted: true
})
Documentation: