Vuex: Automatic state storage

Created on 23 Apr 2016  路  5Comments  路  Source: vuejs/vuex

Why not have state storage automatically integrated with Vuex? The way I was thinking of specifying it was like this:

// cart.js
export default {
    state: {
        items: [],
        status: 'ok' 
    },
    mutations: { ... },
    save: ['items', 'status'] || { all: true }
}

Overriding the default storage provider:

// save-override.js
export default {
    load(module, property) { // invoked on page load
        return JSON.parse(localStorage.getItem(`${module}.${property}`))
    },
    save(state, module, property) {
        localStorage.saveItem(`${module}.${property}`, state[module][property])
    }
}

Saving through a middleware feels clunky somehow, like it doesn't fit with this specific purpose. A default storage implementation can be provided so only 1-liners are needed to enable saving/loading.

Most helpful comment

(assuming 2.0)

I think this can be done by adding a field in the mutation's payload:

store.commit('SOME_MUTATION', {
  save: 'cart'
})

In plugin:

store.subscribe((mutation, state) => {
  const moduleToSave = mutation.payload.save
  if (moduleToSave) {
    saveModule(moduleToSave, state[moduleToSave])
  }
})

All 5 comments

I think this is something that should be solved by the middleware, and I do have plans to improve the way middlewares work right now. Would you explain why you find it clunky?

This is the best I could come up with. I had to rename all of my mutations so that they start with the module name.

// middleware.js
function saveModule(modName, modState) {
    for (let prop in modState) {
        localStorage.setItem(`${modName}.${prop}`, JSON.stringify(modState[prop]))
    }
}
export default {
    onMutation (mutation, state, store) {
        if (mutation.type.startsWith('CART_')) {
            saveModule('cart', state.cart)
        } else if (mutation.type.startsWith('COUNTRIES_')) {
            saveModule('countries', state.countries)
        } // ... on and on
    }
}

The other way I was doing it was like this, which was terribly verbose:

// middleware.js
import * as Mutation from './mutations'

export default {
    onMutation (mutation, state, store) {
        switch(mutation.type) {
            case Mutation.CART_ADD_ITEM:
            case Mutation.CART_REMOVE_ITEM:
                localStorage.setItem('cart.items', JSON.stringify(state.cart.items))
                break
            case Mutation.CART_ERROR:
            case Mutation.CART_OK:
            case Mutation.CART_PROCESSING:
                 localStorage.setItem('cart.status', JSON.stringify(state.cart.status))
                 break
            // ... on and on
        }
    }
}

And nevermind loading the state with a bunch of:

// module.js
export default {
    state: {
        items: JSON.parse(localStorage.getItem('cart.items')) || [],
        status: JSON.parse(localStorage.getItem('cart.status')) || 'ok',
        foo: JSON.parse(localStorage.getItem('cart.foo')) || 0,
        bar: JSON.parse(localStorage.getItem('cart.bar')) || 'zoo'
    },
    // ...
}

Don't get me started with onInit in the middleware, I tried that with PouchDB. More mutations with the exact same sort of code.

This is just something that I find can be relatively easily implemented into Vuex. It gets storage up and running with minimal effort, reduces the lines of code, and reduces the time to refactor if necessary.

(assuming 2.0)

I think this can be done by adding a field in the mutation's payload:

store.commit('SOME_MUTATION', {
  save: 'cart'
})

In plugin:

store.subscribe((mutation, state) => {
  const moduleToSave = mutation.payload.save
  if (moduleToSave) {
    saveModule(moduleToSave, state[moduleToSave])
  }
})

@piknik I've created a module for exactly that :) https://github.com/robinvdvleuten/vuex-persistedstate

I had the same problem and I wanted to
1) save the state to the local storage on every mutation
2) set store state on first load, before anything else executed.

I ended up writing a small vuex plugin for this. The mutations are hardcoded, but you can get the idea:

import {saveToLocal, getFromLocal} from '../../utils/localStore';

const persistUser = (store) => {

    const agreed = getFromLocal('agreed');
    const likes = getFromLocal('likes');

    store.commit('user/AUTHORIZE', agreed);
    store.commit('user/SET_LIKES', likes);

    store.subscribe((mutation, state) => {
        const type = mutation.type;

        if (type === 'user/LIKE' || type === 'user/UNLIKE') {
            saveToLocal('likes', state.user.likes);
        }

        if (type === 'user/ALLOW_TRACKING') {
            saveToLocal('track', mutation.payload);
        }

        if (type === 'user/ALLOW_PUSH') {
            saveToLocal('allowPush', mutation.payload);
        }
    })
};

export default  persistUser;

I hope it can help somebody else... Also note that localStorage does not work as expected in incognito mode and can break your app.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weepy picture weepy  路  3Comments

Ge-yuan-jun picture Ge-yuan-jun  路  3Comments

mbana picture mbana  路  3Comments

gdelazzari picture gdelazzari  路  3Comments

fnlctrl picture fnlctrl  路  4Comments