Giter VIP home page Giter VIP logo

vuex-persist's Introduction

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.js', mode: 'client' }],
}
// ~/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 = new Vuex.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 using 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

vuex-persist's People

Contributors

alexays avatar andressoop avatar arran4 avatar championswimmer avatar cingh-jasdeep avatar glandos avatar goatandsheep avatar hiendv avatar itsmikelowrey avatar kamikazechaser avatar morficus avatar morphatic avatar nallown avatar nicoassinksqills avatar oscarchilds avatar patrickbrouwers avatar richicoder1 avatar robbeman avatar schneefux avatar vikaash02 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vuex-persist's Issues

Date object not being restored from local storage

When a Date object is in the state, it is saved to local storage serialized as a string. Upon reload of the page, the date remains a string.

// excerpt from main.js

const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
})

const store = new Vuex.Store({
  state: {
    allDates: [
      new Date(),
    ]
  },
  mutations: {
    'add-date': state => {
      state.allDates.push(new Date())
    }
  },
  plugins: [
    vuexLocal.plugin,
  ]
})
// excerpt from App.js

<template>
    <div id="app">
        <button @click="$store.commit('add-date')">+</button>
        <ul>
            <li v-for="date in $store.state.allDates">
                {{date.toLocaleDateString()}}
            </li>
        </ul>
    </div>
</template>

Excerpt from error in browser console after using the button to add a date and refreshing the page:
TypeError: date.toLocaleDateString is not a function

Reproduction

You can checkout my reproduction repo. It is a minimal Vue app generated with the default vue-cli (v3) scaffold.

To reproduce the bug, follow these steps:

  • Launch the server using yarn serve.
  • On the page, hit the "+" button to add a date.
  • Refresh the page.
  • Observe that the page is empty and the console is filled with an error message.

Notes

If you do not hit the "+" button, it works, even though there is a date already initialized from the default state. So if you refresh the page with an empty local storage, everything works as expected. I assume that vuex-persist does not save the state to local storage yet, since nothing has triggered a change.

In the production app I found this in, I use TypeScript. The bug is the same there.

how do i set expire time to hours or minutes

const vuexCookie = new VuexPersist({ restoreState: (key, storage) => Cookies.getJSON(key), saveState: (key, state, storage) => Cookies.set(key, state, { expires: 3 }), modules: ['user'] // only save user module })
I do not know what the 3 means. How do i set it to minutes

Save State Performance

Hey, I'm have come across a minor performance issue when saving state in the subscriber. In this instance there is a simple fix available too, but I thought I would reach out to see what people think about it first.

So, you're wanting to persist the data, without getting in the way of your code performance. Then why are we blocking the saveState waiting for the write of data to the data storage in question.

The problem

The current implementation causes waits for localStorage or any other storage method to block the current thread waiting until its performed its operation.

This can cause the application to become non-responsive for a moment while we're writing this state to disk.

/* helper to simulate the delay in localStorage writing */
function wait(ms){
   var start = new Date().getTime();
   var end = start;
   while(end < start + ms) {
     end = new Date().getTime();
  }
}

/* ORIGINAL blocking version */
const myPlugin = store => {
  store.subscribe((mutation, state) => {
    console.time('VuexSubscription')
    wait(7000)
    console.timeEnd('VuexSubscription')
  })
}

The proposal

Make the write method asynchronous. In this example we go one more and use requestIdleCallback in order to find some guaranteed free time and avoid jank.

/* PROPOSED non blocking version */
// Shim `requestIdleCallback`
const requestIdleCallback = window.requestIdleCallback || (cb => {
  let start = Date.now();
  return setTimeout(() => {
    cb({
      didTimeout: false,
      timeRemaining() {
        return Math.max(0, 50 - (Date.now() - start))
      }
    })
  }, 1);
})

const myPlugin = store => {
  store.subscribe((mutation, state) => {
    console.time('VuexSubscription')
    requestIdleCallback(() => {
      wait(7000)
    }, {timeout: 60}) 
    console.timeEnd('VuexSubscription')
  })
}

So, what do you think?

Is it absolutely important to block the main thread waiting for the data to write to the storage?

State Merge Mutation

I managed to find a very weird bug, which I hope was not the case. Sadly the merge technique causes the setState getState to loose reactivity. It might be the way the merge performs it's actions, deepmerge as observed with a competing project appears to not have this issue.

I haven't been able to isolate a solid test for this yet.

So, if you're having state reactivity issues after a refresh, it's likely down to this.

Feature request: allow restore state after app mounted while SSR

When we doing SSR following this guide: https://ssr.vuejs.org/guide/hydration.html

We want to restore the state after app.$mount('#app', true), otherwise the state at local storage would lead different DOM render between server and client.
This is because the state has been restored immediately when Vuex store created in plugins lifecycle, but the DOM rendered from server wouldn't have these state, while app trying to mount, it cause error.

A proposal that we can have a ssrMode option pair with an action to trigger the restore:

const vuexPersist = new VuexPersistence<any, any>({
  ssrMode: true,
})

app.$mount('#app', true)

// restore state from persistence
store.dispatch('RESTORE_ACTION')

loading state from locale storage results in state mutated outside of handler

I have one module auth that is persisted with localStorage. Every time the app is reloaded I get an Do not mutate vuex store state outside mutation handlers error for each state in this module.

store/index.js

const vuexLocal = new VuexPersistence({
  strict: process.env.NODE_ENV !== 'production',
  storage: window.localStorage,
  modules: ['auth']

export default new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  modules: {
    auth, ...
  },
  plugins: [vuexLocal.plugin]
})
})

store/auth.js

const state = {
  token: null,
  sessionId: null,
  userId: null
}

const getters = {
  token (state) { return state.token },
  sessionId (state) { return state.sessionId },
  userId (state) { return state.userId }
}

const mutations = {
  [SET_SESSION] (state, session) {
    state.token = 'Bearer ' + session.token
    state.sessionId = session.id
    state.userId = session.user_id
  },
  [SET_TOKEN] (state, token) {
    state.token = 'Bearer ' + token
  },
  [SET_SESSION_ID] (state, sessionId) {
    state.sessionId = sessionId
  },
  [SET_USER_ID] (state, userId) {
    state.userId = userId
  },
  [CLEAR_SESSION] (state) {
    state.token = null
    state.sessionId = null
    state.userId = null
  }
}

const actions = {
  async [LOGIN] (context, form) {
    let resp = await login('login', form)
    context.commit(SET_TOKEN, resp.data.token)
    context.commit(SET_SESSION_ID, resp.data.id)
    context.commit(SET_USER_ID, resp.data.user_id)
  }
}

one full stacktrace

[Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] Do not mutate vuex store state outside mutation handlers."

(found in <Root>)
warn @ vue.esm.js?efeb:574
logError @ vue.esm.js?efeb:1706
globalHandleError @ vue.esm.js?efeb:1701
handleError @ vue.esm.js?efeb:1690
run @ vue.esm.js?efeb:3198
update @ vue.esm.js?efeb:3170
notify @ vue.esm.js?efeb:680
reactiveSetter @ vue.esm.js?efeb:1006
assignMergeValue @ index.js?981e:856
(anonymous) @ index.js?981e:1128
arrayEach @ index.js?981e:189
baseMerge @ index.js?981e:1111
baseMergeDeep @ index.js?981e:1196
(anonymous) @ index.js?981e:1118
arrayEach @ index.js?981e:189
baseMerge @ index.js?981e:1111
(anonymous) @ index.js?981e:2165
(anonymous) @ index.js?981e:1424
apply @ index.js?981e:169
(anonymous) @ index.js?981e:1227
VuexPersistence.plugin @ index.js?bff2:222
(anonymous) @ vuex.esm.js?358c:351
Store @ vuex.esm.js?358c:351
(anonymous) @ index.js?e3b1:19
(anonymous) @ app.js:7339
__webpack_require__ @ app.js:679
fn @ app.js:89
(anonymous) @ 533:24
(anonymous) @ app.js:4013
__webpack_require__ @ app.js:679
fn @ app.js:89
(anonymous) @ app.js:3909
__webpack_require__ @ app.js:679
(anonymous) @ app.js:725
(anonymous) @ app.js:728
vue.esm.js?efeb:1710 Error: [vuex] Do not mutate vuex store state outside mutation handlers.
    at assert (vuex.esm.js?358c:97)
    at Vue$3.store._vm.$watch.deep (vuex.esm.js?358c:746)
    at Watcher.run (vue.esm.js?efeb:3196)
    at Watcher.update (vue.esm.js?efeb:3170)
    at Dep.notify (vue.esm.js?efeb:680)
    at Object.reactiveSetter [as userId] (vue.esm.js?efeb:1006)
    at assignMergeValue (index.js?981e:856)
    at eval (index.js?981e:1128)
    at arrayEach (index.js?981e:189)
    at baseMerge (index.js?981e:1111)
    at baseMergeDeep (index.js?981e:1196)
    at eval (index.js?981e:1118)
    at arrayEach (index.js?981e:189)
    at baseMerge (index.js?981e:1111)
    at eval (index.js?981e:2165)
    at eval (index.js?981e:1424)
    at apply (index.js?981e:169)
    at eval (index.js?981e:1227)
    at VuexPersistence.plugin (index.js?bff2:222)
    at eval (vuex.esm.js?358c:351)
    at Array.forEach (<anonymous>)
    at new Store (vuex.esm.js?358c:351)
    at eval (index.js?e3b1:19)
    at Object.<anonymous> (app.js:7339)
    at __webpack_require__ (app.js:679)
    at fn (app.js:89)
    at eval (533:24)
    at Object.<anonymous> (app.js:4013)
    at __webpack_require__ (app.js:679)
    at fn (app.js:89)
    at Object.<anonymous> (app.js:3909)
    at __webpack_require__ (app.js:679)
    at app.js:725
    at app.js:728

I am using the latest version still this problem persists

State restored from localforge can not be watched

I have a state like this:

const state = {
  friendList: [],
}

If change the property of friends that restored from vuex-persist,won't trigger any dom update,but if change the property of new pushed friends ,it works.

Object functions not persisted

Hello!

I am currently trying to commit an object to my vuex state that contains functions as some of its attributes.
For example if I commit this to state using a mutation:

{
  attr1: [],
  attr2: '',
  attr3: function()
}

After the mutation my state looks correct:

{
  attr1: [],
  attr2: '',
  attr3: function()
}

But when I refresh the page or check localStorage, attr3 is simply dropped from the object and now looks like this:

{
  attr1: [],
  attr2: ''
}

Any thoughts on the subject? If you need more info just let me know.

0.2.1 breaks null and undefined states

0.2.1 switched to deep-assign which was deprecated 2 years ago (its essentially completely broken) and also doesn't handle null and undefined data types. merge-options should be used instead.

Uncaught TypeError: Cannot convert undefined or null to object (token)
    at assignKey (index.js?e77e:23)
    at assign (index.js?e77e:43)
    at assignKey (index.js?e77e:30)
    at assign (index.js?e77e:43)
    at deepAssign (index.js?e77e:64)
    at VuexPersistence.plugin (index.js?bff2:54)
    at eval (vuex.esm.js?358c:351)
    at Array.forEach (<anonymous>)
    at new Store (vuex.esm.js?358c:351)
    at eval (index.js?e3b1:63)

Where the state is:

const state = {
  token: null
}

Would also be a good idea to get a state with all 7 data types added to tests to prevent issues like this in the future

Default support for Vuex strict mode

I using Vuex in strict mode, an error is thrown as persist attempts to mutate states outside of mutation handlers.

It would be nice if persist could support strict mode in dev. I can turn strict mode off for now, but when the codebase is shared between multiple devs in a team, strict mode ensures good practices.

I don't know whether this is a feature request or if I've missed something?

Opposite of reducer functionality

Hello,

The reducer property is useful for defining all the keys you want to persist. I am looking for a functionality that does the opposite: where I can define a set of keys that I don't want persisted.

Is it currently possible to achieve this? If so, how?

My suggestion would be an exclude array with a set of keys, e.g:
... exclude: ['user.password', 'user.email'] ...

asyncStorage: Commit not persisted when actions are chained

I'm using localForage as my storage as well as Vuex modules. My root store looks like this:

import Vue from "vue";
import Vuex from "vuex";
import localforage from "localforage";
import VuexPersistence from "vuex-persist";
import auth from "./modules/auth";

Vue.use(Vuex);

const local = new VuexPersistence({ storage: localforage });

export default new Vuex.Store({
  modules: { auth },
  mutations: { RESTORE_MUTATION: local.RESTORE_MUTATION },
  plugins: [local.plugin]
});

In my auth module, I've got two actions. The first one logs the user in, and the second one retrieves the user's profile if the login is successful:

const actions = {
  login({ commit, dispatch }) {
    return login().then(
      res => {
        commit(LOGIN_SUCCESS, res); // <-- THIS COMMIT IS CALLED BUT NOT PERSISTED
        return dispatch("getProfile", res.user_id);
      },
      err => commit(LOGIN_FAILURE, err)
    );
  },
  getProfile({ commit }, user_id) {
    commit(PROFILE_REQUEST);
    return api.getProfile(user_id).then(
      res => commit(PROFILE_SUCCESS, res),
      err => commit(PROFILE_FAILURE, err)
    );
  }
};

The problem is that the data in the LOGIN_SUCCESS commit does not get persisted to the store. I know that the login is successful, because I stuck a console.log() in the LOGIN_SUCCESS mutation to make sure it was being called. I also know that it is successful because the getProfile(user_id) API call successfully retrieves the user's profile and stores it in the state when commit(PROFILE_SUCCESS, res) is called. The profile gets persisted, but not the login information.

It seems to work fine in production mode but NOT in development mode. I tried turning strictMode on and off (in both the Vuex store instance and in the VuexPersistence object), but that didn't seem to make a difference.

Oddly, if I put a console.log("hi!") statement at the beginning of the login() action, it seems to work fine in development mode. This made me think the problem may be related to #15, i.e. the RESTORE_MUTATION fires after LOGIN_SUCCESS and therefore overwrites it. That being said, devtools doesn't show that the LOGIN_SUCCESS mutation ever happened. I did try writing a plugin like the one @CosX described here:

const storageReady = () => {
  return store => {
    store.subscribe(mutation => {
      if ("RESTORE_MUTATION" === mutation.type) {
        store._vm.$root.$emit("storageReady");
      }
    });
  };
};

But I couldn't figure out how to get a reference to the $store and therefore the $on() event listener from within a store module.

I clearly don't want to keep a random console.log() statement in my login() action forever. That being said, if I can't use VuexPersistence in development mode, it will make this app very tedious to develop. I would switch to just use localStorage, but I expect this app will have a fair amount of data associated with it and I'd like for people to be able to use it offline as a PWA, so this and this suggest I'm better off sticking with localForage because it uses IndexedDB.

Any suggestions? Thanks!!!

Persisted state overriding nested properties

Say you have a state like:

{ a : { b: 0}}

that gets persisted to local storage. Then you update your store.js state to

{ a : { b: 0, c: 0}}

when that app loads the state in local storage will kill a.c because Object.assign is doing a shallow copy on store.state and savedState.

store.replaceState(Object.assign({}, store.state, savedState))

undefined when accessing the persisted value

Hi,

Would like to ask if this is a bug or there must be something wrong on my implementation. I have set my store below but when I try to access it and add it use it a vue components it is returning undefined and when I checked on the dev tools it does have some value.

import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import checkin from './modules/checkin'
import auto from './modules/auto-accident'
import VuexPersist from 'vuex-persist';

Vue.use(Vuex);

const vuexLocalStorage = new VuexPersist({
    key: 'vuex', // The key to store the state on in the storage provider.
    storage: window.localStorage,
    reducer: (state) => ({
        checkinPatientId: state.checkin.checkinPatientId,
        checkinPatientInfo: state.checkin.checkinPatientInfo,
        autoPatientInfo: state.auto.patientInfo
    })//add modules you wish to save state here
})

//const debug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
    actions,
    getters,
    modules: {
        checkin,
        auto
    },
    plugins: [vuexLocalStorage.plugin],
    strict: false
})

getters.js

export const persistStateCheckinPatientId = state => state.checkinPatientId;//Global getters to access in state
export const persistStateCheckinPatientInfo = state => state.checkinPatientInfo;

Vue components

    export default {
        props:['currentController'],
        data() {
            return {
                checkinName:''
            }
        },
        watch: {
            'persistPatientInfo': function (val, oldVal) {
                if (val) {
                    console.log(val);
                    //this.checkinName = val.FirstName + " " + val.LastName;
                }
            }
        },
        computed: {
            showDefault: function () {
                return !this.showPinkSheet;
            },
            showPinkSheet: function () {
                return this.currentController === 'PinkSheet';
            },
            persistPatientInfo: function () {
                return this.$store.getters.persistStateCheckinPatientInfo;
            }
        }
    }

checking on the dev tools the persist getters is returning undefined, when I try to removed the watch on the components it the persist getters will not be undefined. Also when I try to use the computed persistPatientInfo on the template area it returns undefined again.

image

Using multiple instances of VuexPersist combined with reducers results in wrong state

Hi,

Thanks for making this plugin. Really awesome.

I am trying to use two instances of VuexPersist in my store as a POC before starting on a large Vue project. In my demo I have two store modules (reports and people) which gives me a state tree like this:

image

I have two mutations. One for report and one for people. I am using a single filter on each of my two VuexPersist instances to manage which mutations should be allowed. In this case reports for reports and people for people.

My final store index.js file look like this:
image

As expected I now have two different keys in IndexedDB. I can perfectly trigger mutations which inserts the data in the correct key.

My problem is that, when I reload or initialize my app while having data persisted in indexedDB it now can't seem to figure out to merge the two store modules's state correctly, which ultimately results in having the module's state inserted in the root store state tree.

In my video below I trigger the "setCount" mutation, which bumps the "counter" property I have in my reports object. When reloading my app, the "counter" property now appears in the root of the store state instead inside the reports object. The same goes for the people module.

https://www.screencast.com/t/DIwFlmD9q

I've trying looking into the restoreState option, but as I understand it is only when you are not using storage.

// Nikolaj

Vuex version confusion

Hello!

The README stated that Vuex > 2.1 is supported. On the other hand, package.json specifies "vuex": "^3.0.1".

Which version of Vuex is actually supported?

Error on creating a new instance

I am getting "Uncaught TypeError: Cannot read property 'key' of undefined" when I try to create a new instance of VuexPersistence like this:
const vuexLocal = new VuexPersistence();
However, if I do const vuexLocal = new VuexPersistence({}); It works :v
Any idea?

*PS If it matters, I am not using Typescript.

State not being restored.

I have a list of objects that is stored in vuex. I have setup vuex-persist as instructed but on startup, it doesn't auto restore the state.

I have an action that runs every time the app is started, this action fetches data based on a variable that should be persisted with vuex-persist.

    loadJobs: function ({ commit }) {
      axios({
          method: 'get',
          url: 'http://localhost:9000/document/jobs',
          headers: {'X-BB-SESSION': 'secret-string'},
          params: {
            where: 'timeposted > ' + this.state.lastUpdate //this should have persisted value,
            page: 0,
            recordsPerPage: 50
          }
      })
      .then((response) => {
        if (response.data.data.length > 0){
           console.log(this.state.lastUpdate) // weirdly has the persisted value here
          commit('setJobs', { list: response.data.data })
        }
        commit('setLastUpdateTime', Math.floor(Date.now() / 1000));
      })
      .catch((error) => {
          console.log(error)
      })
  }

inside the response callback of the axios request, this.state.lastUpdate seems to have the right persisted value. other state attributes have this behavior as well.

my question being, when is the state being restored? is there a special scope needed before it can be accessed?

in some cases, I have to mutate a certain state before the persisted version is read by the bound vue component.

Any help will be appreciated. I feel as if I am missing something obvious here

Feature request: Trigger event when async state restored

When using an async storage, it seems to be difficult to know when the state has been replaced (restored).

It'd be ideal if the plugin could trigger an event / mutation when the state has been restored. (e.g. here)

Alternatively, a function could be fed as a configuration parameter, that if it's set, it'd be triggered after replaceState is completed.

Cannot run unit tests

I am attempting to use vue-test-utils and jest to run some unit tests on my vuex actions. I am running into the error below. Any ideas on how I can get this resolved?

 FAIL  test/unit/specs/masternode/Provision.spec.js
  ● Test suite failed to run

    TypeError: Cannot read property '_config' of undefined

    
      
      at new VuexPersistence (node_modules/vuex-persist/dist/index.js:133:44)
      at Object.<anonymous> (src/store/index.js:60:17)
      at Object.<anonymous> (src/contracts/MNProposedMarket.js:19:14)
      at Object.<anonymous> (src/store/modules/app/actions.js:35:25)
      at Object.<anonymous> (src/store/modules/app/index.js:7:16)
      at Object.<anonymous> (src/store/modules/index.js:7:12)
      at Object.<anonymous> (test/unit/specs/masternode/Provision.spec.js:27:16)

Here is my spec so far:

describe('AppActions', () => {
  const vuexLocal = new VuexPersist({
    storage: window.localStorage,
    modules: ['app', 'masternode', 'marketplace'],
  })
  let store

  beforeEach(() => {
    store = new VuexStore ({
      plugins: [vuexLocal.plugin]
    })
  })
})

vue-cli with Element UI integration vuex-persist

it's can save to localstorage,but has error at console:
[Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] Do not mutate vuex store state outside mutation handlers."

Save state conditionally

Thanks for this great plugin!

I am using the plugin to save the users login token, and some private data. I want to use a checkbox in the login form, to choose whether the store should be saved to localstorage. Do you have any ideas how to do this, using this plugin?

Can this be a dev dependency?

Just a question... Does this have to be added as a production dependency or can it be like Vuex and be a dev dependency?

RESTORE_MUTATION does not trigger changes for arrays

So I have problem right now.

My Vuex State has a property called "messages" which is an array, and basically whenever you reassign an array, it doesn't recognise it as a change. So Vue won't see any changes.

The watch functions don't get triggered and the computed properties don't get recomputed.

One way of fixing this is by changing this line of code:

https://github.com/championswimmer/vuex-persist/blob/master/src/index.ts#L85

Into this:

this._vm.$set(state, merge(state, savedState))

This forces Vue to recompute its properties and causes the watch functions to get triggered whereever changes were made.

Here's more information about this https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

Object.assign function not found in older browser

Hello, I use vuex-persist to build a mobile-web app as an android webview.
When I open my mobile-web app using Galaxy S3 (Android version 4.4.4), I got this error

Uncaught TypeError: Object function Object() { [native code] } has no method 'assign

The error occurs because the browser doesn't support object.assign. This problem solved by adding object-assign package

// in dist/index.js
Object.assign = require('object-assign');

localForage support

Hi Arnav,
first of all, thank you for releasing vuex-persist.
You mention on your readme that localforage is supported.
However, new VuexPersistance(storage: localforage}) doesn't work - it surfaces an error related to localforage being asynchronous (VuexPersistance is trying to JSON decode a JS promise).
Any tip how to make this work?

Persist All Vuex Modules

I just have a simple question here...

We are using modules in our current app and I am wondering if we have to specify all the modules we want to persist? Or if we leave out the modules parameter will it persist all modules by default?

Mutation type argument in storage methods

It would be nice to receive the mutation type in setItem, getItem methods, as I see it would be easy to implement passing it to saveState here and it would not interfere with the current behavior, this will give more flexibility on how to deal with different mutations in the storage side, what do you think?

Shared mutations across multiple windows or tabs

Hi, is it possible to share mutations using localForage across multiple windows?
I have a large set of data that i want to share across different pages, my state is stored in the indexeddb but somehow when i open a window my state is never updated when i mutate it in the other. I was using the plugin vuex-shared-mutations which kinda does what i need but its limited to localstorage only.

Thank you

Regards

Seeing: Do not mutate vuex store state outside mutation handlers

Is it expected that when vuex-perist restores data from a storage it will cause this error to show up on the console in development/strict mode?

Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] Do not mutate vuex 
store state outside mutation handlers."

When is the plugin ready to be used?

Loading from localStorage seems immediate when the application starts, but when writing to the vuex store persistence doesn't seem to work until some time has passed.

If I do this in root component the state in localStorage doesn't change:

const root = new Vue({
	el: '#app',
	router,
	store,
	render: (h) => h(App),
	mounted () {
		store.commit('persistent/SET_TEST_VAR', true);
		console.log(store.state.persistent.test_var);
	}
});

If I do this instead it works fine:

setTimeout(() => {
	store.commit('persistent/SET_TEST_VAR', true);
	console.log(store.state.persistent.test_var);
}, 1000);

Reactivity removed on refresh

I am having trouble with my store elements not continuing to be reactive after refreshing the page. My state to start is empty such that:

const state = {
  items: [],
};

I then am using axios to get information from an API and then pushing objects into this array. Below is my mutation:

loadData(state, response) {
    response.items.forEach(function(item) {
      state.items.push(item);
    });
    // Vue.set(state, 'items', response.items);
  },

When I inspect this property in the console, I can see that the reactive getters and setters are added onto these objects within the array. However, when I refresh the page and inspect these objects, the array is reactive but the actual objects are not.

How can I fix this without having to manually go through and fix this on every single module? (I tried to do a global Vue.set() on the state, but that didn't fix it either.)

Wrong documentation about default storage?

I think the documentation in README is wrong.

Under "Constructor Parameters" there is the information "Default: window.localStorage" on the key "storage".

Is this correct? In my testing the parameter storage: window.localStorage is required and not set by default.

Is it better if there is reduced scope with reducer?

    this.plugin = (store: Store<S>) => {
      const savedState = this.restoreState(this.key, this.storage)
      store.replaceState(Object.assign({}, store.state, savedState)) // <-- replace with reduced scope

      this.subscriber(store)((mutation: P, state: S) => {
        if (this.filter(mutation)) {
          this.saveState(this.key, this.reducer(state), this.storage)
        }
      })
    }

Cannot store cyclic object

I am trying to store a web3.js contract object with vuex-persist, but I get the following error:

TypeError: cyclic object value

It looks like there are ways to remove/restore cycles from JSON objects. This feels like something that vuex-persist should do automatically.

Can not get persisted data inside localStorage

Please take a look at this problem, I have been stuck with it for 2 days now.

I have setup a service vuex store, the responsibility of this store to store all the important api call links which I receive from backend. It was working for past few months, but at the moment when my other store modules (for example users here) wants the api link from service store, it receives the link in the beginning, but after a browser refresh the getters receive empty string.

In chrome developer tools, inside Application I see localStorage: {"service": {...}} contains the object, I can not understand how I can not access it? It was working few days ago!

// dispatch the action in component using beforeCreate() lifecycle hook
 beforeCreate() {
      this.$store.dispatch('fetchUsers')
    }
const actions: ActionTree<UserState, RootState> = {

  fetchUsers({getters, commit}) {
    // returns correct link in start of component, but empty string when browser is refreshed!
    const API_URL = getters.usersServiceLink

    return api.get(API_URL)
      .then(response => {
        console.log(response)
        commit(FETCH_USERS_DETAIL_SUCCESS, response)
      })
      .catch(response => {
        commit(FETCH_USERS_DETAIL_FAILURE)
      })
  },
} 
//store.ts
import Vuex, { Payload } from 'vuex'
import VuexPersist from 'vuex-persist'

const vuexLocal = new VuexPersist<RootState, Payload>({
  strictMode: true,
  storage: window.localStorage,
  reducer: state => ({
    service: state.service
  })
})

export default new Vuex.Store<RootState>({
  modules: {},
  mutations: {
    RESTORE_MUTATION: vuexLocal.RESTORE_MUTATION // this mutation **MUST** be named "RESTORE_MUTATION"
  },
  plugins: [vuexLocal.plugin],
  strict: debug
})

store.registerModule overwrites persisted state

Consider this example code:

const store = new Vuex.Store({
  plugins: [createPersistedState(persistedStateOptions)]
});

import user from './user.js';
store.registerModule('user', user);

This will load the persisted state from localStorage (or other) and then it will be overwritten by the state defined in the "user" module. I think I understand why this is happening, but a small warning about this in the documentation could come in handy for some people.

This on the other hand will work fine:

import user from './user.js';

const store = new Vuex.Store({
  modules: {
    user
  },
  plugins: [createPersistedState(persistedStateOptions)]
});

I can use the latter in my project, but I started out with the first implementation and spent some time finding out what prevented the persisted state from being restored.

filter is still saving the mutation to localforge

Currently I have:

const ignoreMutations = [
  'currentUser/setAuthenticated',
  'currentUser/setLoggedIn',
  'resource/setTypes',
  'resource/setCurrentVideo',
]

const vuexPersist = new VuexPersist({
  asyncStorage: true,
  key: 'vuex',
  storage: localForage,
  filter: mutation => !ignoreMutations.includes(mutation.type),
}).plugin

When I check localforge the data has been saved, shouldn't this not be the case?

Allow transform models when restore state

Hello!

I am using typescript (not sure does it matter). I'm trying create model classes for stored data. For example create class CartItem with some methods, like getSimpleProductId. And next problem occurs: when plugin restores state we raw object instead of class. Is it possible create something like reducer callback option, but for restoring? In this callback we can create class objects instead of plain objects.

update Vuex types inside node_modules

Reproduction Steps

-Update to vue.js 2.5

  • This might be because "Previously, we already recommend using ES-style imports (import Vue from ‘vue’) everywhere with “allowSyntheticDefaultImports”: true in tsconfig.json. The new typings will officially move to ES-style import/export syntax, so that config is no longer necessary, and users are required to use ES-style imports in all cases.
    " - from https://goo.gl/pPJeiD

Error

in 'project/node_modules/vuex-persist/node_modules/vuex/types/vue.d.ts'

(9,40): error TS2304: Cannot find name 'Vue'.

tsconfig.json causing error in VS Code

VS Code is yelling at me after adding the package:
No inputs were found in config file '/Users/sean/Desktop/Development/Vuetify/dapp-masternode-vue/node_modules/vuex-persist/tsconfig.json'. Specified 'include' paths were '["src"]' and 'exclude' paths were '["node_modules","test/**/*"]'.

Everything appears to be working properly, it just turns some of VS Code's UI red since it is being recognized as a problem.

How to clear storage?

I'm sorry if this is already described somewhere, I can't seem to find it. When my user logs out of the application I want the storage to be cleared.
I use
window.localStorage.clear();
But when I look in local storage or if I log window.localStorage, I still see my data and it's not cleared.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.