Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear all stores or set them all to its initial state #1118

Closed
JonaMX opened this Issue Jan 4, 2018 · 21 comments

Comments

Projects
None yet
@JonaMX
Copy link

JonaMX commented Jan 4, 2018

What problem does this feature solve?

I have several modules defined on my store, with its own store on each module:

export default new Vuex.Store({
  modules: {
    user,
    items,
    invoices,
    ... // A bunch of other modules
  }
})

The issue is that when the user logs out the app, all the information in the store remains since no mutation is being called to effect them or the set them to its initial state, therefore when another user logs in he can see information of the previous user, I know that I can just create a new mutation to set the store to its initial state, but that means that I'd need to call this mutation for each module that I have.

Is there a way to clear them all?

What does the proposed API look like?

It would be great to have a reserved mutation named 'clearAll' or alike, that resets all the module stores to its initial state as they were defined

...mapMutations([
  'clearAll'
])

Then on the logout method

logout: function () {
  this.$store.dispatch('logout').then(
    () => {
      this.$router.push('/login')
      this.clearAll()
    }
  )
},

@robodude

This comment has been minimized.

Copy link

robodude commented Jan 8, 2018

You can easily implement something like this on your own.

On your 'root' Vuex store you can implement an action that calls each individual modules 'reset' mutation.

    actions: {
        clearAll({ commit }){
            commit("moduleA/reset")
            commit("moduleC/reset")
        }
    }

This gives you greater control over a generic solution because you might have modules that you wouldn't want to clear under some circumstances.

@ktsn

This comment has been minimized.

Copy link
Member

ktsn commented Jan 9, 2018

As @robodude described.

FYI, you can declare a function returning an initial state to reset a state.

function initialState () {
  return { /* .. initial state ... */ }
}

export default {
  state: initialState,

  mutations: {
    reset (state) {
      // acquire initial state
      const s = initialState()
      Object.keys(s).forEach(key => {
        state[key] = s[key]
      })
    }
  }
}

@ktsn ktsn closed this Jan 9, 2018

@DarynHolmes

This comment has been minimized.

Copy link

DarynHolmes commented Mar 28, 2018

Thanks for this. I was wondering why does reset loop over all the keys? Why can't is just set state to initialState() ?

@kiaking

This comment has been minimized.

Copy link
Contributor

kiaking commented Mar 28, 2018

Because if you do that (state = initialState) then the state object will loose it's observer and it won't be reactive anymore. Same as plain object behavior in JavaScript.

const object = { key: 'value' }

function one (o) {
  o.key = 'one'
}

one(object)

console.log(object) // <- { key: 'value' }

function two (o) {
  o = { key: 'two' }
}

two(object)

console.log(object) // <- { key: 'value' } Not modified!
@DarynHolmes

This comment has been minimized.

Copy link

DarynHolmes commented Mar 28, 2018

Thanks @kiaking for the fast and helpful response.

@peacefulseeker

This comment has been minimized.

Copy link

peacefulseeker commented May 24, 2018

Not sure why, but even after creating initialState function, which returns clean object, vue observer was assigned to it after. Probably did something wrong.
But than came across kind of Object.freeze() decision:

// state.js

const initialState = {
  pflegegrad: 0,
  progress: 0,
  result: {
    initial: 0,
    final: 0,
  },
  completedModule: 0,
  currentModule: 1,
  modules: ...nestedObj,
}

export default () => ({
  ...JSON.parse(JSON.stringify(initialState)),
})

And it worked liek a charm. May be the problem was also in deeply nested Objects.

@JFGHT

This comment has been minimized.

Copy link

JFGHT commented Jun 1, 2018

@elbsurfer

This comment has been minimized.

Copy link

elbsurfer commented Jun 12, 2018

@ktsn Thanks for your proposal, but this does not work nested object states. Do you have a solution as well?

@Alendorff

This comment has been minimized.

Copy link

Alendorff commented Jun 27, 2018

Object.keys(s).forEach(key => {
        state[key] = s[key]
      })

In case if there is no additional keys added in state, would Object.assign(state, initialState()) bring the same result? In terms of handling getters correctly and so on.

@NJM8

This comment has been minimized.

Copy link

NJM8 commented Aug 9, 2018

I wanted to chime in with my variation on the great solution provided by @ktsn. I have 10ish vuex modules and like to keep things super DRY. So I defined the same initialState function, but the only other thing I do in the module is put this on the modules state.

In user.js module

function initialState() {
  return {
    status: "",
    profile: {}
  };
}

export const state = {
  initialState: initialState,
  status: initialState().status,
  profile: initialState().profile
};

export const mutations = {
  SET_STATUS: (state, payload) => {
    state.status = payload;
  },
  SET_PROFILE: (state, payload) => {
    state.profile = payload;
  }
};

Then define a global action that will check the presence of initialState on each modules state, and if it has it, loop over the keys and commit the mutation to reset it. I used snake case for all my initial state props and mutations to keep it simple.

In global actions

actions: {
    resetAllState({ dispatch }) {
      for (const currentModule in modules) {
        if (modules[currentModule].state.hasOwnProperty("initialState")) {
          dispatch("resetModuleState", currentModule);
        }
      }
    },
    resetModuleState: ({ commit }, currentModule) => {
      const initialState = modules[currentModule].state.initialState;
      Object.keys(initialState).forEach(key => {
        commit(`${currentModule}/SET_${key.toUpperCase()}`, initialState[key]);
      });
    }
  },

Any feedback or improvements appreciated!

@starfruitsolutions

This comment has been minimized.

Copy link

starfruitsolutions commented Sep 3, 2018

When you do this:

 Object.keys(s).forEach(key => {
        state[key] = initialState[key]
      })

You're using the initial state object for the state, so next time you need to clear it, it won't be in the "initial state". That's why the solution provided by @ktsn requires a function to return a new initial state. You don't need to do this if you:

  1. import the initial state(or clone it) before use
  2. use Object.assign when you actually reset the state.

Here's an example that will clear the entire state including modules:

// import modules
import cart from './modules/cart'
import invoices from './modules/invoices'
import person from './modules/person'

// initial state
let initialState = {
  cart: cart.state,
  invoices: invoices.state,
  person: person.state
}

export default new Vuex.Store({
  modules: {
    cart,
    invoices,
    person
  },
  mutations: {
    reset (state) {
      Object.keys(state).forEach(key => {
        Object.assign(state[key], initialState[key])
      })
    }
  }
})
@sjmcdowall

This comment has been minimized.

Copy link

sjmcdowall commented Sep 24, 2018

@NJM8 -- Love the way you are doing this, although in Typescript I am having some headaches ..LOL

However, one quick question .. Why are your resetXXXX actions and not pure mutations? Well maybe the overall resetAllState should be (although not sure about that) ?? They aren't doing anything really than doing mutations .. no sides effects that I can see..

@NJM8

This comment has been minimized.

Copy link

NJM8 commented Sep 25, 2018

@sjmcdowall Thanks, glad you like it, good luck with the typescript, I have no experience with that.

My resetXXXX are all actions purely out of preference. I try to use actions for everything to have a single pipeline for how data is affected. Some of my early VueJs apps turned out to be a mess because I wasn't strict with that and they were very hard to debug. Now getters get, mutations set, and actions do all the work. Helps me keep things clean and organized. Although yes it can be more code sometimes.

@sjmcdowall

This comment has been minimized.

Copy link

sjmcdowall commented Sep 25, 2018

@NJM8 -- The only thing I don't like about actions -- unless you need them -- is that store.dispatch() always returns a Promise .. which then you have to handle .. (or should.. Typescript sort of forces you to do it) .. whereas pure mutations cannot be Async .. so no async stuff when using them .. but I do make sure a mutation truly only mutates the state .. no other side effects !

@upadhb

This comment has been minimized.

Copy link

upadhb commented Nov 4, 2018

Using @NJM8 solution and store.replaceState(). Here is how i resolved it.

my state many namespaced modules, and inside each module I have

const initialState = () => ({
   // some default state
})

for the module state

state = {
   initialState: initialState(),
   ...initialState(),
}

I've imported the store in the authModule where I am calling the logout().
Inside logout I call the following mutation:

resetState: () => {
        let newState = {};
        Object.keys(store.state).forEach((key) => {
            newState = {
                ...newState,
                [key]: store.state[key].initialState ? store.state[key].initialState : store.state[key],
            };
        });
        store.replaceState(Object.assign({}, newState));
    },

@luckymore

This comment has been minimized.

Copy link

luckymore commented Nov 12, 2018

@kiaking @DarynHolmes

In addition

this.$store.state = {}

will catch

Uncaught Error: [vuex] Use store.replaceState() to explicit replace store state.

@javiertury

This comment has been minimized.

Copy link

javiertury commented Nov 16, 2018

Thanks @upadhb for your solution. However I would like to warn readers that the solution doesn't fit all use cases. Those who want to avoid the stateful singleton problem should not rely on this solution.

The stateful singleton problem can be avoided in vuex modules. It requires the state property of a module to be a function instead of an object. This function returns a new object-state every time it is called.

In this solution state.initialState is an object. state.initialState can't be a function since functions are not serializable. This means that the state must be replaced with an existing object and not a newly generated one, which creates a stateful singleton problem.

@NJM8

This comment has been minimized.

Copy link

NJM8 commented Nov 16, 2018

@upadhb Be careful doing this:

state = {
   initialState: initialState(),
   ...initialState(),
}

Which is different from this:

state = {
   initialState: initialState,
   ...initialState(),
}

In your version you are storing the result of the initial state function not a reference to it. Consider this case:

function initialState() {
  return {
    token: localStorage.getItem("user-token") || "",
    auth_has_error: false,
    auth_error_message: "",
    auth_error_type: ""
  };
}

export const state = {
  initialState: initialState(), 
  token: initialState().token,
  authHasError: initialState().auth_has_error,
  authErrorMessage: initialState().auth_error_message,
  authErrorType: initialState().auth_error_type
};

What happens when your user logs out and you clear the token from local storage? They cannot log in again as you have saved the token in the result of the initialState function on your state. So then you need to reset the initialState defeating it's purpose. Just warning others who see this to be careful how you store the initial state and what you put in it.

@javiertury

This comment has been minimized.

Copy link

javiertury commented Nov 16, 2018

This is the solution I've built on top of @ktsn.

  • Requires state property of a module to be a function.
  • Function must be applied to constructor options and dynamic modules before registering.

It leverages the fact that any module/ can create handlers for root actions. I create reset mutation handlers and reset root action handlers on every module. When the dispatcher is called with the root action reset, every module calls its own reset method.

The function createResetHandler() creates default reset handlers for all modules if they don't exist.

I believe I'm not taking advantage of a bug, since this is core concept of the flux architecture.

import Vuex from 'vuex';

const config = {
  strict: true,
  modules: {
    moduleA,
    ...
  },
}

function createResetHandler(mod) {
  if (mod.hasOwnProperty('modules')) {
    Object.keys(mod.modules).forEach(modName => {
      createResetHandler(mod.modules[modName]);
    });
  }

  if (! mod.hasOwnProperty('state')) return;
  if (typeof mod.state !== 'function') {
    throw 'Vuex module state is not a function';
  }

  if (! mod.hasOwnProperty('mutations')) mod.mutations = {};
  if (! mod.hasOwnProperty('actions')) mod.actions = {};

  if (! mod.mutations.hasOwnProperty('reset')) {
    mod.mutations.reset = function reset(state) {
      replaceObject(state, mod.state());
    };
  }

  if (! mod.actions.hasOwnProperty('reset')) {
    mod.actions.reset = {
      root: true,
      handler ({commit}) {
        commit('reset');
      },
    };
  }
}

createResetHandler(config);

const store = new Vuex.Store(config);

In the case of dynamic module registration:

const moduleDyn = {
  ...
}

createResetHandler(moduleDyn);

store.registerModule('dyn', moduleDyn);

Finally, reset the vuex store with:

dispatch('reset', {}, {root: true});

Eido95 added a commit to Eido95/vue-pizza that referenced this issue Mar 3, 2019

Fix store state replacement
When replacing the store state with simple variable assignment,
the state object looses its observer and won't be reactive anymore.

Instead of replacing the whole state, each of the state's properties
should be replaced.

Source: vuejs/vuex#1118
@j623415

This comment has been minimized.

Copy link

j623415 commented Mar 13, 2019

Just call router.go()

Vuex (and Vue) state is in memory while you are on the same browser page and is reset when you reload the page. If you call router.go(), the page will refresh and all the Vuex stores will go back to their original state. This will also clear any state you may be holding in the components.

@brendon

This comment has been minimized.

Copy link

brendon commented Mar 14, 2019

@Alendorff:

In case if there is no additional keys added in state, would Object.assign(state, initialState()) bring the same result? In terms of handling getters correctly and so on.

You're right, in a Vuex module something like this works very well provided you don't want to keep any keys added after initialisation:

defaultState = ->
  isVisible: false
  header: ''
  value: {}
  positive_action: ''

export default
  state: defaultState()
  mutations:
    showModal: (state, payload) ->
      state.header = payload.header
      state.positive_action = payload.positive_action
      state.isVisible = true
    hideModal: (state) ->
      state = Object.assign state, defaultState()

Excuse the coffee script :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.