I am using Redux for state management.
How do I reset the store to its initial state?
For example, let’s say I have two user accounts (u1
and
I found that the accepted answer worked well for me, but it triggered the ESLint no-param-reassign
error - https://eslint.org/docs/rules/no-param-reassign
Here's how I handled it instead, making sure to create a copy of the state (which is, in my understanding, the Reduxy thing to do...):
import { combineReducers } from "redux"
import { routerReducer } from "react-router-redux"
import ws from "reducers/ws"
import session from "reducers/session"
import app from "reducers/app"
const appReducer = combineReducers({
"routing": routerReducer,
ws,
session,
app
})
export default (state, action) => {
const stateCopy = action.type === "LOGOUT" ? undefined : { ...state }
return appReducer(stateCopy, action)
}
But maybe creating a copy of the state to just pass it into another reducer function that creates a copy of that is a little over-complicated? This doesn't read as nicely, but is more to-the-point:
export default (state, action) => {
return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}
Define an action:
const RESET_ACTION = {
type: "RESET"
}
Then in each of your reducers assuming you are using switch
or if-else
for handling multiple actions through each reducer. I am going to take the case for a switch
.
const INITIAL_STATE = {
loggedIn: true
}
const randomReducer = (state=INITIAL_STATE, action) {
switch(action.type) {
case 'SOME_ACTION_TYPE':
//do something with it
case "RESET":
return INITIAL_STATE; //Always return the initial state
default:
return state;
}
}
This way whenever you call RESET
action, you reducer will update the store with default state.
Now, for logout you can handle the like below:
const logoutHandler = () => {
store.dispatch(RESET_ACTION)
// Also the custom logic like for the rest of the logout handler
}
Every time a userlogs in, without a browser refresh. Store will always be at default.
store.dispatch(RESET_ACTION)
just elaborates the idea. You will most likely have an action creator for the purpose. A much better way will be that you have a LOGOUT_ACTION
.
Once you dispatch this LOGOUT_ACTION
. A custom middleware can then intercept this action, either with Redux-Saga or Redux-Thunk. Both ways however, you can dispatch another action 'RESET'. This way store logout and reset will happen synchronously and your store will ready for another user login.
With Redux if have applied the following solution, which assumes I have set an initialState in all my reducers (e.g. { user: { name, email }}). In many components I check on these nested properties, so with this fix I prevent my renders methods are broken on coupled property conditions (e.g. if state.user.email, which will throw an error user is undefined if upper mentioned solutions).
const appReducer = combineReducers({
tabs,
user
})
const initialState = appReducer({}, {})
const rootReducer = (state, action) => {
if (action.type === 'LOG_OUT') {
state = initialState
}
return appReducer(state, action)
}
From a security perspective, the safest thing to do when logging a user out is to reset all persistent state (e.x. cookies, localStorage
, IndexedDB
, Web SQL
, etc) and do a hard refresh of the page using window.location.reload()
. It's possible a sloppy developer accidentally or intentionally stored some sensitive data on window
, in the DOM, etc. Blowing away all persistent state and refreshing the browser is the only way to guarantee no information from the previous user is leaked to the next user.
(Of course, as a user on a shared computer you should use "private browsing" mode, close the browser window yourself, use the "clear browsing data" function, etc, but as a developer we can't expect everyone to always be that diligent)
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case RESET_STORE: {
state = initialState
}
break
}
return state
}
You can also fire an action which is handled by all or some reducers, that you want to reset to initial store. One action can trigger a reset to your whole state, or just a piece of it that seems fit to you. I believe this is the simplest and most controllable way of doing this.
If you are using redux-actions, here's a quick workaround using a HOF(Higher Order Function) for handleActions
.
import { handleActions } from 'redux-actions';
export function handleActionsEx(reducer, initialState) {
const enhancedReducer = {
...reducer,
RESET: () => initialState
};
return handleActions(enhancedReducer, initialState);
}
And then use handleActionsEx
instead of original handleActions
to handle reducers.
Dan's answer gives a great idea about this problem, but it didn't work out well for me, because I'm using redux-persist
.
When used with redux-persist
, simply passing undefined
state didn't trigger persisting behavior, so I knew I had to manually remove item from storage (React Native in my case, thus AsyncStorage
).
await AsyncStorage.removeItem('persist:root');
or
await persistor.flush(); // or await persistor.purge();
didn't work for me either - they just yelled at me. (e.g., complaining like "Unexpected key _persist ...")
Then I suddenly pondered all I want is just make every individual reducer return their own initial state when RESET
action type is encountered. That way, persisting is handled naturally. Obviously without above utility function (handleActionsEx
), my code won't look DRY (although it's just a one liner, i.e. RESET: () => initialState
), but I couldn't stand it 'cuz I love metaprogramming.