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
},
})
Centralizing the Resetting of the State Usually, you would use the combineReducers function to create a single root reducer for your redux store: import { combineReducers } from 'redux'; const usersDefaultState = []; const users = (state = usersDefaultState, { type, payload }) => //...
When we refresh page in a web-app, the state always resets back to the initial values which in not a good thing when you try to build some large web-app like e-commerce. We can manually do the state persistent using the native JavaScript localStorage.
There are two main ways to initialize state for your application. The createStore method can accept an optional preloadedState value as its second argument. Reducers can also specify an initial value by looking for an incoming state argument that is undefined , and returning the value they'd like to use as a default.
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
})
},
})
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
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.
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.
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With