28

I have seen solutions for clearing/resetting the store after logout but did not understand how to implement the same functionality for the following way of setting up the redux store.

Store.js:


import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import authReducer from './ducks/authentication'
import snackbar from './ducks/snackbar'
import sidebar from './ducks/sidebar'
import global from './ducks/global'
import quickView from './ducks/quickView'
import profileView from './ducks/profileView'

const store = configureStore({
  reducer: {
    auth: authReducer,
    snackbar,
    sidebar,
    global,
    quickView,
    profileView,
  },
  middleware: [...getDefaultMiddleware()],
})

export default store



Here is how all the reducers implemented using createAction and createReducer from @reduxjs/toolkit.

snackbar.js:


import { createAction, createReducer } from '@reduxjs/toolkit'

export const handleSnackbar = createAction('snackbar/handleSnackbar')

export const openSnackBar = (
  verticalPosition,
  horizontalPosition,
  message,
  messageType,
  autoHideDuration = 10000
) => {
  return async dispatch => {
    dispatch(
      handleSnackbar({
        verticalPosition,
        horizontalPosition,
        message,
        autoHideDuration,
        messageType,
        isOpen: true,
      })
    )
  }
}

export const closeSnackbar = () => {
  return dispatch => {
    dispatch(handleSnackbar({ isOpen: false }))
  }
}

const initialState = {
  verticalPosition: 'bottom',
  horizontalPosition: 'center',
  message: '',
  autoHideDuration: 6000,
  isOpen: false,
  messageType: 'success',
}

export default createReducer(initialState, {
  [handleSnackbar]: (state, action) => {
    const {
      isOpen,
      verticalPosition,
      horizontalPosition,
      message,
      autoHideDuration,
      messageType,
    } = action.payload
    state.isOpen = isOpen
    state.verticalPosition = verticalPosition
    state.horizontalPosition = horizontalPosition
    state.message = message
    state.autoHideDuration = autoHideDuration
    state.messageType = messageType
  },
})



Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
sunil b
  • 685
  • 2
  • 8
  • 21

5 Answers5

64

As per Dan Abramov's answer, create a root reducer which will simply delegate the action to your main or combined reducer. And whenever this root reducer receives a reset type of action, it resets the state.

Example:

const combinedReducer = combineReducers({
  first: firstReducer,
  second: secondReducer,
  // ... all your app's reducers
})

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

So, if you have configured your store with @reduxjs/toolkit's configureStore, it might look like this:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export default configureStore({
  reducer: {
    counter: counterReducer,
    // ... more reducers
  },
});

where configureStore's first parameter reducer accepts a function (which is treated as root reducer) or an object of slice reducers which is internally converted to root reducer using combineReducers.

So, now instead of passing object of slice reducers (shown above), we can create and pass root reducer by ourselves, here is how we can do it:

const combinedReducer = combineReducers({
  counter: counterReducer,
  // ... more reducers
});

Now, lets create a root reducer which does our reset job when needed:

const rootReducer = (state, action) => {
  if (action.type === 'counter/logout') { // check for action type 
    state = undefined;
  }
  return combinedReducer(state, action);
};

export default configureStore({
  reducer: rootReducer,
  middleware: [...getDefaultMiddleware()]
});

Here is CodeSandbox

Ajeet Shah
  • 18,551
  • 8
  • 57
  • 87
  • 6
    This answer should be in the docs of redux-toolkit, thnks! P.S: take a look at the CodeSandbox implementation, Ajeet added more comments there. – Bruno de Oliveira Sep 03 '20 at 11:56
  • Any chance of a TypeScript version of the CodeSandbox with types? – Jamie Oct 10 '20 at 17:27
  • @Jamie The sandbox link in my answer is a Typescript version. What types are you looking at? Or what exactly is your question? If you have a different question, feel free to ask a new question and you may post the link here in comment. – Ajeet Shah Oct 10 '20 at 18:00
  • 2
    I forked your sandbox and added types- you can view that [here](https://codesandbox.io/s/reset-state-redux-toolkit-forked-u17iv), you can also see my answer below. – Jamie Oct 11 '20 at 12:27
  • I wanted to know when and how this action of logout is going to be called. Comparing `action.type === "counter/logout"`. Does this mean that state will be undefined every-time logout function defined inside counter slice is going to be called. If so, do we need to create this `logout` reducer function in all our states. Also in codesandbox, there is no code inside the logout reducer of counter slice. So i couldn't understand what is going on. @AjeetShah – Aakash Sharma May 26 '23 at 09:10
  • @AakashSharma `Does this mean that state will be undefined every-time logout function defined inside counter slice is going to be called?` : Yes. `do we need to create this logout reducer function in all our states`: No. `there is no code inside the logout reducer of counter slice`: yes, it is empty because I wanted to handle it at `rootReducer`. We just need one logout action. We can create that action in just one slice and handle that at root reducer. – Ajeet Shah May 26 '23 at 11:52
25

I wanted to extend Ajeet's answer so that it is accessible to those who want complete type safety throughout their Redux store.

The key differences are that you need to declare a RootState type, which is documented in the RTK docs

const combinedReducer = combineReducers({
  counter: counterReducer
});

export type RootState = ReturnType<typeof combinedReducer>;

And then in your rootReducer, where you are executing your logout function, you want to maintain type safety all the way down by giving the state param the RootState type, and action param AnyAction.

The final piece of the puzzle is setting your state to an empty object of type RootState instead of undefined.

const rootReducer: Reducer = (state: RootState, action: AnyAction) => {
  if (action.type === "counter/logout") {
    state = {} as RootState;
  }
  return combinedReducer(state, action);
};

I forked Ajeet's answer on CodeSandbox, added the required types, and you can view it here.

Jamie
  • 3,105
  • 1
  • 25
  • 35
14

If you're looking to reset each slice to its initial state (unlike setting the entire state to an empty object) you can use extraReducers to respond to a logout action and return the initial state.

In auth.tsx:

const logout = createAction('auth/logout')

In foo.tsx:

const initialState = {
  bar: false,
}

const fooSlice = createSlice({
  name: 'foo',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(logout, () => {
      return initialState
    })
  },
})
Stefan Bajić
  • 374
  • 4
  • 14
  • 1
    this was preferred approach for me – oldo.nicho Oct 27 '21 at 01:22
  • 1
    For me as well, because: (1) this will set the state to the initial state, not an undefined state; (2) this avoids the switch on action.type, which is normally not present in the 'new way' of doing it with redux toolkit – bugshake Sep 28 '22 at 13:25
  • 1
    I'm using this in combination with the `resetApiState` [action](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#resetapistate) for resetting RTK Query. – blwinters Oct 03 '22 at 18:26
  • I think this could just go in the normal reducer object i.e. `reducers { resetState: () => initialState }` + `export const { resetState } = fooSlice.actions` – Dominic Oct 12 '22 at 11:34
  • The problem with this approach, is that this would only reset the "foo" namespaced reducer data. – MarkSkayff Nov 16 '22 at 18:24
  • Why are we using `extraReducers` here? The same can be achieved using normal reducers. Note that we don't do `state = initialState` but `return initialState`. This has something to do with immer library that is used in RTK. – Piyush Aggarwal Feb 21 '23 at 17:35
  • @blwinters whereabouts did you put the resetApiState? Trying to work out the best place it should happen – Jarrod McGuire Jun 15 '23 at 09:56
5

A simplified example with two reducers:

// actions and reducer for state.first
const resetFirst = () => ({ type: 'FIRST/RESET' });

const firstReducer = (state = initialState, action) => {
    switch (action.type) {
        // other action types here

        case 'FIRST/RESET':
            return initialState;

        default:
            return state;
    }
};


// actions and reducer for state.second
const resetSecond = () => ({ type: 'SECOND/RESET' });

const secondReducer = (state = initialState, action) => {
    switch (action.type) {
        // other action types here

        case 'SECOND/RESET':
            return initialState;

        default:
            return state;
    }
};


const rootReducer = combineReducers({
    first: firstReducer,
    second: secondReducer
});

// thunk action to do global logout
const logout = () => (dispatch) => {
    // do other logout stuff here, for example logging out user with backend, etc..

    dispatch(resetFirst());
    dispatch(resetSecond());
    // Let every one of your reducers reset here.
};
timotgl
  • 2,865
  • 1
  • 9
  • 19
0

The simple solution - just add a reducer like this...

    resetList: (state) => {
      return (state = []);
    },

... and call it with a button:


  const handleResetList = () => {
    dispatch(resetList());
  };

  return (
    <div>
      <div>List</div>
      <button onClick={handleResetList}>Reset</button>
ydrea
  • 71
  • 1
  • 1
  • 8