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?
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()
}
)
},
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.
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]
})
}
}
}
Thanks for this. I was wondering why does reset loop over all the keys? Why can't is just set state to initialState() ?
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!
Thanks @kiaking for the fast and helpful response.
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.
Why Vuex doesn't have something like this?
@ktsn
https://stackoverflow.com/questions/35622588/how-to-reset-the-state-of-a-redux-store
@ktsn Thanks for your proposal, but this does not work nested object states. Do you have a solution as well?
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.
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!
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:
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])
})
}
}
})
@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..
@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.
@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 !
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));
},
@kiaking @DarynHolmes
In addition
this.$store.state = {}
will catch
Uncaught Error: [vuex] Use store.replaceState() to explicit replace store state.
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.
@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.
This is the solution I've built on top of @ktsn.
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});
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.
@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
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:
- import the initial state(or clone it) before use
- use
Object.assignwhen 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]) }) } } })This is quite succinct. Nice!
Almost perfect, @Toyrone and @starfruitsolutions .
I had to change this:
// initial state let initialState = { cart: cart.state, invoices: invoices.state, person: person.state }
To this:
// initial state
let initialState = {
cart: { ...cart.state },
invoices: { ...invoices.state },
person: { ...person.state }
}
I also inverted the loop, so I don't need to reset all states:
Object.keys(initialState).forEach(key => {
Object.assign(state[key], initialState[key])
})
Thank you!
Hello guys,
I am glad you did not have to work with arrays and I am glad you did not have a state with nested objects/arrays.
For all of you who have to deal with cases like
state = {
results: {
0: {},
1: {}
},
selectedResults: [
{ name: 'foo'}
{ name: 'bar')
]
}
This is the solution
const resetObjectsRecursively = (state, defaultState) => {
for (const [key, value] of Object.entries(defaultState)) {
if(Object.prototype.toString.call(state[key]) === '[object Object]')
resetObjectsRecursively(state[key], defaultState[key]);
else if(Object.prototype.toString.call(state[key]) === '[object Array]') {
state[key].length = 0;
for (let elem of defaultState[key]) {
state[key].push(elem);
}
}
else {
state[key] = value
}
}
};
Notice how you do not assign anything not to loose the "damn" references in case of objects and arrays (which turn out to be like objects anyways).
A variation on what @j623415 posted. Amazing when one line of code in my logout function takes care of all of this mess...
this.$router.go(this.$router.currentRoute)
I had a similar problem, double harder due to the fact, that some of my modules can gain additional keys the longer they are in use (I don't need to observe them though). But since I have something of a "base prop value", depending on its type, and all of my modules are quite flat, I ended up using this:
const defaultState = {...getDefaultState()};
Object.keys(state).forEach(key => {
if (defaultState[key]) {
state[key] = defaultState[key]
} else {
if (typeof state[key] === 'string') {
state[key] = '';
}
if (typeof state[key] === 'boolean') {
state[key] = false;
}
if (typeof state[key] === 'number') {
state[key] = 0;
}
}
});
It is ugly and will probably be changed to something more reusable in the near future, but for now it works.
I'm not very qualified to comment, but my thought when I read your post @LucasPilarski was that it sounds odd that your Store module object would gain base keys at runtime. Is this a recommended approach?
@brendon I guess not, but I don't know the exact list of keys that the user will provide for the store, so I had to improvise.
@LucasPilarski, when you reset, do you need to keep the user-defined keys or would it be sufficient to have a base store object userKeys: {} for example that can get reset to empty when you reset the store, and just slowly fills up with those key:values over time until reset again?
Dynamic loaded Vuex modules does not clear any states, and we doesnt give a 'kc about why there is preserveState(true, but false by default also doesnt make a sense) option, if vuex really doesnt clear anything on unregisterModule. So, by the way, there example of using it inside module. To clear separate module before unregister.
import Vue from 'vue';
import Vuex from 'vuex';
import abcd from "@/store/modules/abcd";
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production'
const vueStore = new Vuex.Store({
namespaced: true,
modules: {
abcd,
},
strict: debug,
});
export default vueStore;
const _State = {
ob_ject: {
title: "Title",
description: "Description",
array: []
}
};
const initState = () => ({
...JSON.parse(JSON.stringify(_State)),
})
//mutations...
m_Clear(state){
console.log("state is",JSON.stringify(state.ob_ject));
console.log("_State before is",JSON.stringify(_State));
//this is so stupid way to make it, but... better to use Object.assign if you are using unregisterModule
Object.keys(state).forEach(index => {
state[index] = _State[index]
});
console.log("state after is",JSON.stringify(state.ob_ject));
console.log("_State after is",JSON.stringify(_State));
},
export default {
namespaced: true,
state: initState,
getters,
mutations,
actions
};
or just simple: state: ()=>({...JSON.parse(JSON.stringify(_State))}),.
or if u 'r using unregisterModule and wanna use Object.assign instead json.parse:
const initState = () => Object.assign({},initialState);
//or
state: Object.assign({},initialState);
But idk why it's called twice if we are using vue dev tools to. You can log it by adding console.log output.
Without vue-devtools:

With vue-devtools:

@yyx990803, @Akryum is this a bug? I think thats not normal, what about cases when i wanna make state dynamically? For example :
const initState = () => {
i++;
console.log(i);
if(i==1)return Object.assign({},_State);
if(i==2)return Object.assign({},_StateSecond);//we gonna get different state inside vue-devtools (vuex)
}
So, find your destroyer, add:
(if you are using function like ...JSON.parse(JSON.stringify(obj)) you don't need clear mutation, coz everytime it's gonna return new object, on registerModule event's)
this.$store.commit("abcd/m_Clear");
this.$store.unregisterModule('abcd')
this.$store.commit("someanothermodule/m_EmptyMutation");//need to call this coz vue dev tools doesnt update vuex state after unregister
If you don't wanna use JSON.parse(JSON.stringify)
But also seems like GC doesnt clear this object's, after changing route, idk why, but it's still in undone list in log's.
@ktsn, @NJM8, @kiaking, @starfruitsolutions is that right way to use and clear separate module state on unregisterModule? Or no?
Maybe someone should add something about clearing on register/unregisterModule page's in docs?
My vue-devtools settings:

Changed to new-vuex backend, doesnt make a sense:

@brendon No, not really, but I am not sure if in the future some of them would not be necessary for the app to run properly, so I do that just in case. And as I said earlier - this is probably a temporary method, that will be changed sometime in the future. For now, it works and that is all I need.
So, i fixed it, if anyone else need this, you can get it here, but you also need recomplie and sign ext for yourself.
https://github.com/vuejs/vue-devtools/issues/1058#issuecomment-527620457 fix.
this.$store.registerModule('abcd', AbcdModule,{devtools_getStateDirectly:true});
or
this.$store.registerModule(['abcd'], AbcdModule,{devtools_getStateDirectly:true});.
Roger @LucasPilarski :) Sounds good to me. I was just trying to make sure I hadn't overlooked any edge case scenario in my code too.
I see a lot of people here are clearing the store on logout. What is an alternative to that? Let's say I want to calculate this size of my store at some point in the app once it's over 1 gb, I will clear it.
Where would a place like that be?
Please, could we have a reset() method added to Vuex.Store Instance Methods ?
I have found a plugin named vuex-reset what seems is working for me. May be will help somebody vuex-reset.
Anyway for me will be better that kind of implementation will be inside the vuex, because as other people explained here, when you work in an authenticated scenario after login is a mandatory practice reset the store.
https://github.com/vuejs/vuex/pull/1666 may help you
Because of author closed my PR, I created a tiny package for that: vuex-extensions
Check out the example on CodeSandbox.
Creating Vuex.Store
import Vuex from 'vuex'
import { createStore } from 'vuex-extensions'
export default createStore(Vuex.Store, {
plugins: []
modules: {}
})
// Vue Component
this.$store.reset()
// Vuex action
modules: {
sub: {
actions: {
logout() {
this.reset()
}
}
}
}
location.href = '/' maybe?
Try that.
setTimeout(()=>
{
window.location.href = "/";
},3000);
```javascript
import Vue from "vue";
import Vuex from "vuex";
import login from "./modules/login";
import profile from "./modules/profile";
let initialState ={
login:login.state,
profile:profile.state,
}
//Convert object in string
const COPY = JSON.stringify(initialState)
export default new Vuex.Store({
modules: {
login,
profile,
},
mutations: {
RESET_STATE(state) {
//Convert string in object
let copyState = JSON.parse(COPY);
Object.keys(state).forEach(key => {
Object.assign(state[key], copyState[key])
})
}
}
});
It feels kinda hacky to do it as follows. But it works..
...
mutations: {
resetState(state) {
Object.keys(state).forEach((key) => delete state[key])
Object.assign(state, initialState)
},
}
...
Any objections I'm missing? I believe reactivity is still in order. But again, it feels hacky..
Most helpful comment
As @robodude described.
FYI, you can declare a function returning an initial state to reset a state.