How to reset the state of a Redux store?

How to reset the state of a Redux store?

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 u2).
Imagine the following sequence of events:

User u1 logs into the app and does something, so we cache some data in the store.
User u1 logs out.
User u2 logs into the app without refreshing the browser.

At this point, the cached data will be associated with u1, and I would like to clean it up.
How can I reset the Redux store to its initial state when the first user logs out?

Solutions/Answers:

Solution 1:

One way to do that would be to write a root reducer in your application.

The root reducer would normally delegate handling the action to the reducer generated by combineReducers(). However, whenever it receives USER_LOGOUT action, it returns the initial state all over again.

For example, if your root reducer looked like this:

const rootReducer = combineReducers({
  /* your app’s top-level reducers */
})

You can rename it to appReducer and write a new rootReducer delegating to it:

const appReducer = combineReducers({
  /* your app’s top-level reducers */
})

const rootReducer = (state, action) => {
  return appReducer(state, action)
}

Now we just need to teach the new rootReducer to return the initial state after USER_LOGOUT action. As we know, reducers are supposed to return the initial state when they are called with undefined as the first argument, no matter the action. Let’s use this fact to conditionally strip the accumulated state as we pass it to appReducer:

 const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    state = undefined
  }

  return appReducer(state, action)
}

Now, whenever USER_LOGOUT fires, all reducers will be initialized anew. They can also return something different than they did initially if they want to because they can check action.type as well.

To reiterate, the full new code looks like this:

const appReducer = combineReducers({
  /* your app’s top-level reducers */
})

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    state = undefined
  }

  return appReducer(state, action)
}

Note that I’m not mutating the state here, I am merely reassigning the reference of a local variable called state before passing it to another function. Mutating a state object would be a violation of Redux principles.

In case you are using redux-persist, you may also need to clean your storage. Redux-persist keeps a copy of your state in a storage engine, and the state copy will be loaded from there on refresh.

First, you need to import the appropriate storage engine and then, to parse the state before setting it to undefined and clean each storage state key.

const rootReducer = (state, action) => {
    if (action.type === SIGNOUT_REQUEST) {
        // for all keys defined in your persistConfig(s)
        storage.removeItem('persist:root')
        // storage.removeItem('persist:otherKey')

        state = undefined;
    }
    return appReducer(state, action);
};

Solution 2:

I’d like to point out that the accepted comment by Dan Abramov is correct except we experienced a strange issue when using the react-router-redux package along with this approach. Our fix was to not set state to undefined but rather still use the current routing reducer. So I would suggest implementing the solution below if you are using this package

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    const { routing } = state
    state = { routing } 
  }
  return appReducer(state, action)
}

Solution 3:

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.

Solution 4:

 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.

Solution 5:

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)
}

Solution 6:

Just a simplified answer to the best answer:

const rootReducer = combineReducers({
    auth: authReducer,
    ...formReducers,
    routing
});


export default (state, action) => (
    action.type === 'USER_LOGOUT'
        ? rootReducer(undefined, action)
        : rootReducer(state, action)
)