vuex-persist
    TypeScript icon, indicating that this package has built-in type declarations

    3.1.3 • Public • Published

    vuex-persist

    A Typescript-ready Vuex plugin that enables you to save the state of your app to a persisted storage like Cookies or localStorage.

    Paypal Donate

    Info : GitHub stars npm npm license

    Status : Build Status codebeat badge Codacy Badge Code Climate codecov

    Sizes : npm:size:gzip umd:min:gzip umd:min:brotli

    Table of Contents

    Table of contents generated with markdown-toc

    Features

    • 📦 NEW in v1.5
      • distributed as esm and cjs both (via module field of package.json)
      • better tree shaking as a result of esm
    • 🎗 NEW IN V1.0.0
      • Support localForage and other Promise based stores
      • Fix late restore of state for localStorage
    • Automatically save store on mutation.
    • Choose which mutations trigger store save, and which don't, using filter function
    • Works perfectly with modules in store
    • Ability to save partial store, using a reducer function
    • Automatically restores store when app loads
    • You can create mulitple VuexPersistence instances if you want to -
      • Save some parts of the store to localStorage, some to sessionStorage
      • Trigger saving to localStorage on data download, saving to cookies on authentication result

    Compatibility

    • VueJS - v2.0 and above
    • Vuex - v2.1 and above

    Installation

    Vue CLI Build Setup (using Webpack or some bundler)

    npm install --save vuex-persist

    or

    yarn add vuex-persist

    Transpile for target: es5

    This module is distributed in 3 formats

    • umd build /dist/umd/index.js in es5 format
    • commonjs build /dist/cjs/index.js in es2015 format
    • esm build /dist/esm/index.js in es2015 format

    When using with Webpack (or Vue CLI 3), the esm file gets used by default. If your project has a es6 or es2015 target, you're good, but if for backwards compatibility, you are compiling your project to es5 then this module also needs to be transpiled.

    To enable transpilation of this module

    // in your vue.config.js
    module.exports = {
      /* ... other config ... */
      transpileDependencies: ['vuex-persist']
    }

    Directly in Browser

    <!-- We need lodash.merge so get lodash first -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuex-persist"></script>

    Tips for NUXT

    This is a plugin that works only on the client side. So we'll register it as a ssr-free plugin.

    // Inside - nuxt.config.js
    export default {
      plugins: [
        { src: '~/plugins/vuex-persist', ssr: false }
      ]
    }
    // ~/plugins/vuex-persist.js
    import VuexPersistence from 'vuex-persist'
     
    export default ({ store }) => {
      new VuexPersistence({
      /* your options */
      }).plugin(store);
    }

    Usage

    Steps

    Import it

    import VuexPersistence from 'vuex-persist'

    NOTE: In browsers, you can directly use window.VuexPersistence

    Create an object

    const vuexLocal = new VuexPersistence({
      storage: window.localStorage
    })
     
    // or in Typescript
     
    const vuexLocal = new VuexPersistence<RootState>({
      storage: window.localStorage
    })
     

    Use it as Vue plugin. (in typescript)

    const store = new Vuex.Store<State>({
      state: { ... },
      mutations: { ... },
      actions: { ... },
      plugins: [vuexLocal.plugin]
    })

    (or in Javascript)

    const store = {
      state: { ... },
      mutations: { ... },
      actions: { ... },
      plugins: [vuexLocal.plugin]
    }

    Constructor Parameters -

    When creating the VuexPersistence object, we pass an options object of type PersistOptions. Here are the properties, and what they mean -

    Property Type Description
    key string The key to store the state in the storage
    Default: 'vuex'
    storage Storage (Web API) localStorage, sessionStorage, localforage or your custom Storage object.
    Must implement getItem, setItem, clear etc.
    Default: window.localStorage
    saveState function
    (key, state[, storage])
    If not using storage, this custom function handles
    saving state to persistence
    restoreState function
    (key[, storage]) => state
    If not using storage, this custom function handles
    retrieving state from storage
    reducer function
    (state) => object
    State reducer. reduces state to only those values you want to save.
    By default, saves entire state
    filter function
    (mutation) => boolean
    Mutation filter. Look at mutation.type and return true
    for only those ones which you want a persistence write to be triggered for.
    Default returns true for all mutations
    modules string[] List of modules you want to persist. (Do not write your own reducer if you want to use this)
    asyncStorage boolean Denotes if the store uses Promises (like localforage) or not (you must set this to true when suing something like localforage)
    Default: false
    supportCircular boolean Denotes if the state has any circular references to itself (state.x === state)
    Default: false

    Usage Notes

    Reducer

    Your reducer should not change the shape of the state.

    const persist = new VuexPersistence({
      reducer: (state) => state.products,
      ...
    })

    Above code is wrong You intend to do this instead

    const persist = new VuexPersistence({
      reducer: (state) => ({products: state.products}),
      ...
    })

    Circular States

    If you have circular structures in your state

    let x = { a: 10 }
    x.x = x
    x.x === x.x.x // true
    x.x.x.a === x.x.x.x.a //true

    JSON.parse() and JSON.stringify() will not work. You'll need to install flatted

    npm install flatted
    

    And when constructing the store, add supportCircular flag

    new VuexPersistence({
      supportCircular: true,
      ...
    })

    Examples

    Simple

    Quick example -

    import Vue from 'vue'
    import Vuex from 'vuex'
    import VuexPersistence from 'vuex-persist'
     
    Vue.use(Vuex)
     
    const store = new Vuex.Store<State>({
      state: {
        user: { name: 'Arnav' },
        navigation: { path: '/home' }
      },
      plugins: [new VuexPersistence().plugin]
    })
     
    export default store

    Detailed

    Here is an example store that has 2 modules, user and navigation We are going to save user details into a Cookie (using js-cookie) And, we will save the navigation state into localStorage whenever a new item is added to nav items. So you can use multiple VuexPersistence instances to store different parts of your Vuex store into different storage providers.

    Warning: when working with modules these should be registered in the Vuex constructor. When using store.registerModule you risk the (restored) persisted state being overwritten with the default state defined in the module itself.

    import Vue from 'vue'
    import Vuex, { Payload, Store } from 'vuex'
    import VuexPersistence from 'vuex-persist'
    import Cookies from 'js-cookie'
    import { module as userModule, UserState } from './user'
    import navModule, { NavigationState } from './navigation'
     
    export interface State {
      user: UserState
      navigation: NavigationState
    }
     
    Vue.use(Vuex)
     
    const vuexCookie = new VuexPersistence<State, Payload>({
      restoreState: (key, storage) => Cookies.getJSON(key),
      saveState: (key, state, storage) =>
        Cookies.set(key, state, {
          expires: 3
        }),
      modules: ['user'], //only save user module
      filter: (mutation) => mutation.type == 'logIn' || mutation.type == 'logOut'
    })
    const vuexLocal = new VuexPersistence<State, Payload>({
      storage: window.localStorage,
      reducer: (state) => ({ navigation: state.navigation }), //only save navigation module
      filter: (mutation) => mutation.type == 'addNavItem'
    })
     
    const store = new Vuex.Store<State>({
      modules: {
        user: userModule,
        navigation: navModule
      },
      plugins: [vuexCookie.plugin, vuexLocal.plugin]
    })
     
    export default store

    Support Strict Mode

    This now supports Vuex strict mode (Keep in mind, NOT to use strict mode in production) In strict mode, we cannot use store.replaceState so instead we use a mutation

    You'll need to keep in mind to add the RESTORE_MUTATION to your mutations See example below

    To configure with strict mode support -

    import Vue from 'vue'
    import Vuex, { Payload, Store } from 'vuex'
    import VuexPersistence from 'vuex-persist'
     
    const vuexPersist = new VuexPersistence<any, any>({
      strictMode: true, // This **MUST** be set to true
      storage: localStorage,
      reducer: (state) => ({ dog: state.dog }),
      filter: (mutation) => mutation.type === 'dogBark'
    })
     
    const store = new Vuex.Store<State>({
      strict: true, // This makes the Vuex store strict
      state: {
        user: {
          name: 'Arnav'
        },
        foo: {
          bar: 'baz'
        }
      },
      mutations: {
        RESTORE_MUTATION: vuexPersist.RESTORE_MUTATION // this mutation **MUST** be named "RESTORE_MUTATION"
      },
      plugins: [vuexPersist.plugin]
    })

    Some of the most popular ways to persist your store would be -

    • js-cookie to use browser Cookies
    • window.localStorage (remains, across PC reboots, untill you clear browser data)
    • window.sessionStorage (vanishes when you close browser tab)
    • localForage Uses IndexedDB from the browser

    Note on LocalForage and async stores

    There is Window.Storage API as defined by HTML5 DOM specs, which implements the following -

    interface Storage {
      readonly length: number
      clear(): void
      getItem(key: string): string | null
      key(index: number): string | null
      removeItem(key: string): void
      setItem(key: string, data: string): void
      [key: string]: any
      [index: number]: string
    }

    As you can see it is an entirely synchronous storage. Also note that it saves only string values. Thus objects are stringified and stored.

    Now note the representative interface of Local Forage -

    export interface LocalForage {
      getItem<T>(key: string): Promise<T>
      setItem<T>(key: string, data: T): Promise<T>
      removeItem(key: string): Promise<void>
      clear(): Promise<void>
      length(): Promise<number>
      key(keyIndex: number): Promise<string>
      _config?: {
        name: string
      }
    }

    You can note 2 differences here -

    1. All functions are asynchronous with Promises (because WebSQL and IndexedDB are async)
    2. It works on objects too (not just strings)

    I have made vuex-persist compatible with both types of storages, but this comes at a slight cost. When using asynchronous (promise-based) storages, your state will not be immediately restored into vuex from localForage. It will go into the event loop and will finish when the JS thread is empty. This can invoke a delay of few seconds.

    How to know when async store has been replaced

    As noted above, the store is not immediately restored from async stores like localForage. This can have the unfortunate side effect of overwriting mutations to the store that happen before vuex-persist has a chance to do its thing. In strict mode, you can create a plugin to subscribe to RESTORE_MUTATION so that you tell your app to wait until the state has been restored before committing any further mutations. (Issue #15 demonstrates how to write such a plugin.) However, since you should turn strict mode off in production, and since vuex doesn't currently provide any kind of notification when replaceState() has been called, starting with v2.1.0 vuex-persist will add a restored property to the store object to let you know the state has been restored and that it is now safe to commit any mutations that modify the stored state. store.restored will contain the Promise returned by calling the async version of restoreState().

    Here's an example of a beforeEach() hook in vuex-router that will cause your app to wait for vuex-persist to restore the state before taking any further actions:

    // in src/router.js
    import Vue from 'vue'
    import Router from 'vue-router'
    import { store } from '@/store' // ...or wherever your `vuex` store is defined
     
    Vue.use(Router)
     
    const router = new Router({
      // define your router as you normally would
    })
     
    const waitForStorageToBeReady = async (to, from, next) => {
      await store.restored
      next()
    }
    router.beforeEach(waitForStorageToBeReady)
     
    export default router

    Note that on the 2nd and subsequent router requests to your app, the Promise in store.restored should already be in a "resolved" state, so the hook will not force your app to wait for additional calls to restoreState().

    Unit Testing

    Jest

    When testing with Jest, you might find this error -

    TypeError: Cannot read property 'getItem' of undefined
    

    This is because there is no localStorage in Jest. You can add the following Jest plugins to solve this https://www.npmjs.com/package/jest-localstorage-mock

    Install

    npm i vuex-persist

    DownloadsWeekly Downloads

    81,803

    Version

    3.1.3

    License

    MIT

    Unpacked Size

    224 kB

    Total Files

    20

    Last publish

    Collaborators

    • championswimmer