DEV Community

Vue Mastery team for Vue Mastery

Posted on • Originally published at vuemastery.com on

Refresh-proof your Pinia Stores

Refresh-proof your Pinia Stores

Written by David Nwadiogbu

A common use case for all data stores is a need to persist data. That way, if a user refreshes the page or begins a new session we can store (persist) the current state and use it later on when the user comes back.

While Vue’s state management library, Pinia, has a lot of great features, it does not automatically persist application state. So how do you persist Pinia state upon a browser refresh? In this tutorial, we’ll be exploring three different ways you can refresh-proof your Pinia stores.

Project Setup

To get started, if you don’t already have Pinia installed in your Vue app, add it now. In the root directory, run the following command to install Pinia:

yarn add pinia

# or with npm
npm install pinia
Enter fullscreen mode Exit fullscreen mode

Then update your main.js to use Pinia:

📄 main.js

import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount("#app");
Enter fullscreen mode Exit fullscreen mode

Create a stores folder under src and add a file called counter.js with the following code:

📁 src/stores/counter.js

import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", {
  state: () => {
    return { 
      count: 0
     };
  },
  actions: {
    increment(value = 1) {
      this.count += value;
    },
    decrement(value = 1) {
      this.count -= value;
    },
    reset() {
      this.count = 0;
    }
  },
  getters: {
    doubleCount: (state) => {
      return state.count * 2;
    },
    squareCount: (state) => {
      return state.count ** 2;
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

We’ve created a state called count with multiple actions and getters that can dictate our state’s behavior.

Let’s display this in our app to test that it works.

Update your App.vue file to look like this:

📁 src/App.vue

<script setup>
import { useCounterStore } from "./stores/counter"

const store = useCounterStore(); _//initialize the store_

</script>

<template>
  <h1>Counter App</h1>
  <p>Count is {{ store.count }}</p>
  <p>Double count is {{ store.doubleCount }}</p>

  <button @click="store.increment(1)">Add</button>
  <button @click="store.decrement(1)">Subtract</button>
  <button @click="store.reset">Reset</button>
</template>

<style scoped>
</style>
Enter fullscreen mode Exit fullscreen mode

When we run the app, everything works as it should, but when we reload our page, the value of count gets reset to its initial state: 0.

There is nothing broken here; Pinia does not by itself persist the state. If that’s the behavior we want, we have to solve for that ourselves. So let’s look at different ways we can solve this problem by persisting our store upon page reloads.

using Watch

The most common way to persist values is to save them in local storage and use the saved values to hydrate our Pinia store when the app loads.

We can use the watch method from Vue to achieve this.

Update your main.js to include the following code:

import { createApp, watch } from "vue";
...

watch(
  pinia.state,
  (state) => {
    localStorage.setItem("counter", JSON.stringify(state.counter));
  },
  { deep: true }
  );
Enter fullscreen mode Exit fullscreen mode

and update your counter.js to include the following code:

state: () => {
    if (localStorage.getItem("counter"))
      return JSON.parse(localStorage.getItem("counter"));
    return {
      count: 0,
    };
  },
Enter fullscreen mode Exit fullscreen mode

Let’s examine what our code is doing here. In the first code block, we use the watch method to detect when there are changes to our state. If there’s a change, we want to update our localStorage with this change. In the second code block, after our store is created, we check if there’s already a value for our counter state in localStorage. If there is, we then set that value to our initial value for our counter.

useLocalStorage from VueUse

The VueUse composable useLocalStorage provides a much simpler approach to persisting values in our store with much less code. useLocalstorage will check our local storage for store values and will also smartly handle serialization for us based on the data type of provided default value.

To get started, install the VueUse library:

yarn add @vueuse/core

# or with npm
npm i @vueuse/core
Enter fullscreen mode Exit fullscreen mode

Now we can import useLocalStorage into our code and get persistence in just one line of code.

Update your counter store to include the following code:

📁 src/stores/counter.js

import { useLocalStorage } from "@vueuse/core"

state: () => {
    return {
     count: useLocalStorage('count', 0), _//useLocalStorage takes in a key of 'count' and default value of 0_
    };
  },
Enter fullscreen mode Exit fullscreen mode

Using a plugin

The final approach we’re going to look at is using a plugin. A great example is the Pinia plugin called persistedstate. This is a tiny package that is highly customizable, compatible with both Vue 2 and Vue 3, and (at the time of this writing) is being actively maintained.

To use this, we have to install it:

yarn add pinia-plugin-persistedstate

# or with npm
npm i pinia-plugin-persistedstate
Enter fullscreen mode Exit fullscreen mode

Update your main.js file to use the plugin:

📁 src/main.js

...

import piniaPluginPersistedState from "pinia-plugin-persistedstate"

const pinia = createPinia();

pinia.use(piniaPluginPersistedState)
Enter fullscreen mode Exit fullscreen mode

Finally, add a property called persist and set it to true in your counter store object.

📁 src/stores/counter.js

export const useCounterStore = defineStore("counter", {
  state: () => {
    return {
     count: 0,
    };
  },
  ...
  persist: true,
});
Enter fullscreen mode Exit fullscreen mode

You can also configure how a store is persisted by specifying options to the persist property. For example, our plugin persists data in local storage by default but we can modify it to use session storage by updating the storage configuration that exists in the persist property:

export const useCounterStore = defineStore("counter", {
  state: () => {
    return {
     count: 0,
    };
  },
  ...
  persist: {
    storage: sessionStorage, _// data in sessionStorage is cleared when the page session ends._
  },
});
Enter fullscreen mode Exit fullscreen mode

All the available configuration options are explained here.

Where to go from here

Although we used a very simple example to demonstrate how to persist state in Pinia, the same approach would apply when working with more complex stores.

Please keep in mind that data stored in localStorage is not private and you should be careful with storing sensitive information there.

If you’re curious about Pinia, you should check out our Pinia Fundamentals course taught by Sandra Rogers.

Originally published at https://www.vuemastery.com on October 31, 2022.


Top comments (0)