Persisting Vue.Js Pinia States: A Step-By-Step Guide
Published: 2023-12-07 11:52:19

by [Joakim Gakure](https://blog.openreplay.com/authors/joakim-gakure)


<blockquote><em>
State persistence maintains data across page reloads. It’s a user-friendly experience bonus to ensure users pick up where they left off. As a Vue.js developer, Vue uses Pinia as its official state management library. This guide will discuss state persistence and how you achieve it across Vue.js applications.
</em></blockquote>


<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>

If you have used [Pinia](https://pinia.vuejs.org/) in your projects, you might lack persistence. It doesn't include built-in persistence across reloads. You need to know this is not a bug, and it's designed to be agnostic about how the state persists works. This gives you the authority to choose how you want to persist in your data.

Let's highlight a few points to why persistence is important:

- In a server-based setup, saving states improves app performance as saved data will be loaded locally.
- Persistence creates data consistency between different user sessions.
- You get an enhanced user experience as users will save their preferences and other activities that they can continue from where they left off.

So, let's dive into this guide and learn how to add Pinia persist. Along the guide, you will use methods such as LocalStorage and useStorage to get your state persistence right.

## Creating a Sample Vue.js Pinia App

Before diving into these strategies, you'll need a working Vue Pinia-inspired application. This [guide](https://blog.openreplay.com/managing-state-in-vue-with-pinia-orm/) shows you how to get started. However, you will use the following basic notes app to comfortably follow along.

- First, ensure you have created your Vue app and added Pinia for state management:

![Creating Vue.js App](https://blog.openreplay.com/images/persisting-vue-pinia-state/images/image1.png)

- Navigate to the `src/stores` directory, create a `NoteStore.js` file, and manage your store as follows:

```javascript
import { defineStore } from "pinia";
export const useNoteStore = defineStore({
  id: "note",
  state: () => ({
    notes: [],
  }),
  actions: {
    createNote(title) {
      this.notes.push({ id: Date.now(), title, done: false });
    },
    isDone(note) {
      note.done = !note.done;
    },
    deleteNote(note) {
      this.notes = this.notes.filter((t) => t.id !== note.id);
    },
  },
});
```

- Go ahead and create your notes app right in your `src/App.vue` as follows:

```javascript
<template>
  <div>
    <h1>Notes App</h1>
    <input
      v-model="newNote"
      @keyup.enter="createNote"
      placeholder="Add a new Note"
    />
    <ul>
      <li v-for="note in notes" :key="note.id">
        <input type="checkbox" :checked="note.done" @change="isDone(note)" />
        <span>{{ note.title }}</span>
        <button @click="deleteNote(note)">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { useNoteStore } from "./stores/NoteStore";
import { ref, computed } from "vue";
const noteStore = useNoteStore();
const newNote = ref("");
const notes = computed(() => noteStore.notes);
const createNote = () => {
  if (newNote.value.trim() !== "") {
    noteStore.createNote(newNote.value);
    newNote.value = "";
  }
};
const isDone = (note) => noteStore.isDone(note);
const deleteNote = (note) => noteStore.deleteNote(note);
</script>
```

You're ready to go. When you interact with this simple app, Pinia works as expected:

![-](https://blog.openreplay.com/images/persisting-vue-pinia-state/images/image2.png)

However, on page refresh, every state you have is lost. And this is where the journey to Pinia persistence starts. In the following sections, you will learn the strategies to add to this app and make it persistent.

## Persisting Pinia Using LocalStorage

LocalStorage is a browser-based key-value store for browsers. Using [LocalStorage and Pinia](https://thriveread.com/persist-pinia-with-localstorage/), your web browser will persist data on the client side. This way, reloads will fetch the exiting states directly from the LocalStorage.

To add LocalStorage to your store, you'll focus on the `getItem` and `setItem` methods from LocalStorage. Pinia will look up to these methods, and based on your store `actions`, any state changes will be recorded to the browser. Here's how you will implement this concept:

In your `NoteStore.js` file, you need to modify your `state`. In this case, you will instruct Pinia to fetch from LocalStorage using `getItem` as follows:

```javascript
export const useNoteStore = defineStore({
  id: "note",
  state: () => ({
    notes: JSON.parse(localStorage.getItem("notes")) || [],
  }),
});
```

- Next, you must save any Pinia `actions` to LocalStorage. Here, you first need to create a method that will save the data to LocalStorage whenever it changes using `setItem` as follows:

```javascript
actions: {
  persistToLocalStorage() {
    localStorage.setItem("notes", JSON.stringify(this.notes));
  },
}
```

Finally, every action you have will point to the created `persistToLocalStorage` so you can save state mutations to localStorage as follows:

```javascript
actions: {
  // Your setItem localStorage method
  persistToLocalStorage() {
    localStorage.setItem("notes", JSON.stringify(this.notes));
  },

  createNote(title) {
    this.notes.push({ id: Date.now(), title, done: false });
    // Allow createNote() method to use LocalStorage
    this.persistToLocalStorage();
  },

  isDone(note) {
    note.done = !note.done;
    // Any updated note properties should reflect to localStorage
    this.persistToLocalStorage();
  },
  deleteNote(note) {
    this.notes = this.notes.filter((t) => t.id !== note.id);
    // You delete action must delete from localStorage as well
    this.persistToLocalStorage();
  },
},
```

Let’s test how the app is working so far. LocalStorage key-value should work and persist in states as follows:

![LocalStorage key-value](https://blog.openreplay.com/images/persisting-vue-pinia-state/images/image3.png)


<CTA_Middle_Programming />



## Using VueUse and useStorage

[VueUse](https://vueuse.org/) is a list of Vue Composition API utilities used to abstract how your Vue app should work based on your preference. Among these utilities, you have [useStorage](https://vueuse.org/core/useStorage/) function that manages Vue storage reactively.

To add `useStorage()`, you must install VueUse and use an explicit import as follows:

- Install VueUse:

```bash
npm i @vueuse/core
```

- Add your VueUse import to your store. In this case, your `NoteStore.js` file:

```javascript
import { useStorage } from "@vueuse/core";
```

- All you need now is to add `useStorage()` to smartly watch for any state’s changes:

```javascript
state: () => ({
  // Listen to any state changes
  notes: useStorage('notes', [])
}),
```

## Using Persisted State Plugin

If you want an out-of-the-box solution, the [persistedstate plugin](https://www.npmjs.com/package/pinia-plugin-persistedstate) is specifically created to manage your states. Its storage mechanisms allow you to access LocalStorage while allowing you to specify which state properties to persist. To use it:

- First, install your plugin:

```bash
npm i pinia-plugin-persistedstate
```

- Initialize this plugin to Pinia. Navigate to your `src/main.js` file and add the following configurations:

```javascript
import { createApp } from "vue";
// Add your plugin import
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import { createPinia } from "pinia";
import App from "./App.vue";
const app = createApp(App);
// initialize Pinia
const pinia = createPinia();
// Use the plugin
pinia.use(piniaPluginPersistedstate);
app.use(createPinia());
app.use(pinia);
app.mount("#app");
```

- Navigate to your store and set Pinia persist to true as follows:

```javascript
export const useNoteStore = defineStore({
  // Set persist to true
  persist: true,
// ... The rest of the code
});
```

Go ahead and interact with the app. By default, this plugin will use the browser LocalStorage to persist Pinia as follows:

![-](https://blog.openreplay.com/images/persisting-vue-pinia-state/images/image4.png)

## Conclusion

This guide showed you how to harness the power of Pinia and use persistent states using LocalStorage, VueUse, and the persisted state plugin. Now, your states can still be accessed across page reloads.

Using these approaches, you can additionally persist your arrays and objects. However, note that these strategies use LocalStorage to store your states. Thus, you will have data [size limits](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria) of up to 5MB. Remember, be mindful of security risks to sensitive data such as passwords and tokens.