Redux MapDispatchToProps not functioning - reactjs
So I'm new to Redux and I'm trying to get this base model working so I can quickly work on a small personal project, I set everything up and have no errors but I'm trying to test and my function doesn't work so I was hoping someone could point out what I've missed.
I've followed multiple different tutorials and each has a different approach so that has me lost a bit so I apologize for that.
My store.js looks like so
import rootReducer from "./reducers";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
I've used a combineReducers in my index.js in reducers folder and the auth: points to the authReducer.js file, which is this
const INIT_STATE = {
email: "",
password: "",
isLoggedIn: "false"
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
isLoggedIn: action.value
};
default:
return state;
}
};
Now What I'm aiming for is to have a button that changes that "IsLoggedIn" initial state to a true string instead of a false, I've went into my actions folder and made an authActions.js which looks like so
import { IS_LOGGED_IN_CHANGE } from "../actions/types";
import store from "../store";
export const isLoggedInChange = value => {
return dispatch => {
dispatch({
type: IS_LOGGED_IN_CHANGE,
value
});
};
};
And Finally I want to show you my component page which is showing all this, It's looking like so
import { connect } from "react-redux";
import styles from "./Landing.module.css";
import { isLoggedInChange } from "../../actions/authActions";
class Landing extends Component {
makeTrue = () => {
isLoggedInChange("true");
};
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
}
render() {
return (
<div className={styles.background}>
<button onClick={this.makeTrue}>MAKE TRUE</button>
{this.props.isLoggedIn}
</div>
);
}
}
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn
});
const mapDispatchToProps = dispatch => ({
isLoggedInChange: value => dispatch(isLoggedInChange(value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Landing);
Can you tell if I dropped anything making this? why is the button not changing the store state? TIA
Two problems here. You're calling your action creator directly not props.isLoggedInChange
makeTrue = () => {
this.props.isLoggedInChange("true");
};
And you need to spread the old state inside your action
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};
Isn't the point of my mapDispatchToProps to be able to use the function right away as I was doing
Yes, the problem is mapDispatchToProps inject a function (or multiple functions) wrapped in dispatch into your props.
import { actionCreator } from './actions
const mapDispatchToProps = dispatch =>({
actionCreator : () => dispatch(actionCreator)
})
Now you have two actionCreator, one globally available in the scope (which is your action creator) and props.actionCreator which is the original action creator wrapped in dispatch. So when you call actionCreator() from inside your component it won't throw any errors (cause there is a function named actionCreator in the scope, but you will be calling the wrong function, the right one is located at props.actionCreator.
Why do I need to spread the state?
A reducer is a pure function which receives a state and action and returns the new state. When you just return
return {
isLoggedIn : true
}
You're actually overwriting the original state (which contains other properties), so first you need to spread the original state to maintain it's structural integrity and them overwrite the properties you want
return{
...state,
isLoggedIn : !state.isLoggedIn
}
Redux state is immutable so you need to return a brand new instance of state, change your reducer state to the below.
export default (state = INIT_STATE, action) => {
switch (action.type) {
case IS_LOGGED_IN_CHANGE:
console.log(action);
return Object.assign({}, state, {
isLoggedIn: action.value
});
default:
return state;
}
};
The key difference there being the
return Object.assign({}, state, {
isLoggedIn: action.value
});
Object.assign in the way I'm using it here combines the state object into a brand new object. Check out immutability within redux reducers and I'd recommend adding redux-immutable-state-invariant as a dev package, it can detect when you're directly modifying state and help point out errors like this
Return the state with the new value for isLoggedIn. Use the reducer like this:
case IS_LOGGED_IN_CHANGE:
console.log(action);
return {
...state,
isLoggedIn: action.value
};
Related
React Redux store update doesn't trigger component rerender
I'm new in Redux and have a problem with rerendering after the store changed. I have found many similar problems here on SO but still can't solve my issue. I have a monthly task(event) calendar with multiple tasks. The Calendar is the main component and some level deeper there are multiple TaskItem components. At the first render, the calendar and the tasks are rendered fine (In this case without employee names). In the Calendar component I trigger loading employees with a useEffect hook. I can see the network request on my console. Besides this, the console logs in the action, and in the reducer also show the employee list. And the Redux devtool also shows the loaded employees. Still the mapStateToProps on TaskItem shows a completly empty state. What I'm doing wrong? Here is my related code: Calendar: const Calendar = ({startDay, tasks, loadEmployeesAction}) => { useEffect(()=>{ loadEmployeesAction(); },[]); ... } export default connect(null, {loadEmployeesAction})(Calendar); TaskItem: const TaskItem = ({task, onTextEdit, onTaskView, saveTask, employees }) => { ... } const mapStateToProps = (state) => { console.log('Actual state is: ', state); return { employees: state.employees } } export default connect(mapStateToProps)(TaskItem); Reducer: export const employeeReducer = (state = [], action) => { switch (action.type) { case actionType.EMPLOYEES_LOADED: console.log('Reducer - Employees loaded:', action ); return action.payload.employees; default : return state; } } Actions: const employeesLoaded = (employees) => { return {type: actionType.EMPLOYEES_LOADED, payload: { employees } } } export const loadEmployeesAction = () => { return (dispatch) => { return employeeApi.getAllEmployees().then(emps => { console.log('Action - Employees loaded: ', emps); dispatch(employeesLoaded(emps)); }) } } Root reducer: export const rootReduxReducer = combineReducers({ employees: employeeReducer });
I found the error. It was a very clumsy mistake. All of my posted code was fine, but I put the store creation in a component that was rerendered again and again so my store was recreated again and again.
The reducer code seems to be not as the redux pattern. So usually the state object is not directly replaced with a different object. Instead only the part of the state that needs to be changed is only with some non-mutating operation like spread operator. So I think the reducer code should be changed like export const employeeReducer = (state = [], action) => { switch (action.type) { case actionType.EMPLOYEES_LOADED: return {...state,employees:action.payload.employees} default : return state; } } if the response from the API is in the form [{"employee_name":"name","employee_age":24},.....]
How do I manually dispatch actions to a store created with configureStore?
I have a project where half of it was made with classes, and the other half is being made with hooks and Redux. For this, I have created a store with configureStore() from Redux Toolkit and provided it using the Provider component. In a very minimal way, the store is set up as follows: const userSlice = createSlice({ name: 'user', initialState: { user: {} }, reducers: { validate: (state, action) => state.user = action.payload } }) const store configureStore({ reducer: { user: userSlice.reducer } }) There are two components - a new one, functional, which uses the useSelector() hook, and an older one, which is class based, but needs to use this sasme store to dispatch an action. To do this, I import the store and fire store.dispatch({type: 'user/validate', payload: newUser}); from the class component. I receive no errors, yet nothing happens at all. I tracked my input from DevTools' Redux plugin, and I can see the state does not change, so I assume my manual call to dispatch is somehow wrong. What I expect to happen is for the state to update, which would trigger a re-render of the component that uses useSelector
The following way is a safe way to dispatch actions without misspelling the type string. Extract the action from reducer const userSlice = createSlice({ name: 'user', initialState: { user: {} }, reducers: { validate: (state, action) => { state.user = action.payload } } }) // <------------------ // Action creators are generated for each case reducer function export const { validate } = userSlice.actions export const store = configureStore({ reducer: { user: userSlice.reducer } }) Dispatch the action as so store.dispatch(validate(newUser))
Issue I'm going to say the issue is that you are trying to both mutate your state object in the reducer function and return it. See Mutating and Returning State In any given case reducer, Immer expects that you will either mutate the existing state, or construct a new state value yourself and return it, but not both in the same function! Solution Just mutate the state, don't return it. reducers: { validate: (state, action) => { state.user = action.payload; }, } If you want your class-based component to subscribe to your redux store then you can still use the connect Higher Order Component from react-redux. Example: import { connect } from 'react-redux'; import { validate } from '../path/to/userSlice'; class MyComponent extends Component { ... // In a function you can simply dispatch the validate action // as it was wrapped in a call to dispatch already and injected // as a prop. this.props.validate(somePayloadValue); ... } const mapDispatchToProps = { validate }; export default connect(null, mapDispatchToProps)(MyComponent);
How to fetch updated state in Component while using Redux
I am new to redux, I am getting confused about how to get State from Redux Store when we have multiple Reducer. let Say this is my combineReducer import apiCallInProgress from './ApiStattusReducer'; import login from './loginReducer'; const rootReducer = combineReducers({ login, apiCallInProgress }); export default rootReducer; // below is my Login Reducer const initialState = { isAuthenticated: false, user: {} }; export function loginReducer(state = initialState, action = {}) { console.log(action.user); switch (action.type) { case types.SET_CURRENT_USER: return { ...state, isAuthenticated: true, user: action.user } default: return state } } export default loginReducer; // now I want to access user from my component, // I wrote as function mapStateToProps(state) { return { login: state.login } } componentDidMount() { const { login } = this.props console.log("set user didmount:" + login.user) } componentDidUpdate() { const { login } = this.props console.log("set user didupdate:" + login.user) } I am not able to get the state of user in the component but when I am pressing login button console.log(action.user) showing proper output in console . The variable names we mentioned inside combineReducer, the same name do I need to use inside mapStateToProps func to fetch the state. I am very much confused. Someone, please explain.
I think you're doing everything right till you get to the component (although difficult to fully determine without actually testing the code). The main issue is, as far as I can see, you're not actually dispatching the action so login.user is never being set. The connect method of react-redux has two function parameters - mapStateToProps, which you're using correctly, and mapDispatchToProps, which it doesn't look like you're using. mapDispatchToProps is a method to pass Redux actions into props, which when invoked, will fire the action, which will in turn be picked up by the reducer, which will return a new state object. Create a new directory called actions in the root of your app. Inside it, create a file called loginActions.js, and within that, put something like the following: export function setCurrentUser(user) { return (dispatch) => { dispatch({ type: 'SET_CURRENT_USER', user) } } import this function into your component, then in your connect function, add mapDispatchToProps import { connect } from 'react-redux' import { setCurrentUser } from './actions/loginActions' // component code const mapDispatchToProps = { setCurrentUser: setCurrentUser } const mapStateToProps = (state) => { return { login: state.login } } connect(mapStateToProps, mapDispatchToProps)(YourComponentName) In your componentDidMount method, you can now call this action: componentDidMount() { this.props.setCurrentUser('myUser') } in componentDidUpdate, the user should now be available: componentDidUpdate(prevProps) { if (prevProps.login !== this.props.login) { console.log(this.props.login) } } I hope this helps. Let me know if you need any more help.
Class object not working properly in reducer
I have a user model (es6 class) and I'm creating a object using the new keyboard and passing that to the initialState to my userReducer function. How can I update the model based on action. E.g. If I try to dispatch an action to change the isLogging in userModel then the prevState and nextState is same in logger. https://i.ibb.co/0CBSZ5v/Screenshot-from-2019-04-19-19-07-44.png User Reducer import { USER } from '../constants' import type { IUserInitialState, IUserAction } from '../types' import { UserModel } from '../models' const initialState: IUserInitialState = new UserModel() export const userReducer = (state: IUserInitialState = initialState, action: IUserAction): Object => { console.log(state) switch (action.type) { case USER.LOGIN_REQUEST: console.log(state) initialState.userIsLogging = action.payload return initialState default: return state } } ------------------------------ User Action export const loginRequest = (type: boolean): Object => { return { type: USER.LOGIN_REQUEST, payload: type } } User Model export class UserModel { user: IUserModel = { username: '', password: '', isLogging: false } set userModel(userObject: IUserModel) { this.user = userObject } set userIsLogging(logging: boolean) { this.user.isLogging = logging } get userIsLogging() { return this.user.isLogging } } [1]: https://i.ibb.co/0CBSZ5v/Screenshot-from-2019-04-19-19-07-44.png
You are using reducer wrong. 1- When you create a state, make sure it's just a primitive type without any methods. 2- A reducer is responsible of creating a new state on any action. But you are only returning initial state. You should have something like case USER.LOGIN_REQUEST: console.log(state) initialState.userIsLogging = action.payload return { ...state, userIsLogging: action.payload, } 3- You might want to check sagas. You don't have to handle all this async logic yourself
A reducer must be a pure function. It should returns a new state based on a previous state and an action. Please do not mutate the previous state. Instead, create a new instance, make the changes you want there and finally returns this new instance. I would highly recommend you to watch the videos from the redux github page. No better explanation that the one from the redux author.
How to clear all states in redux store when user logs out? [duplicate]
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?
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 in response to the 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') { return appReducer(undefined, action) } 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') { return appReducer(undefined, action) } return appReducer(state, action) } 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') return appReducer(undefined, action); } return appReducer(state, action); };
Dan Abramov's answer 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 the 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) }
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.
Just a simplified answer to Dan Abramov's answer: const rootReducer = combineReducers({ auth: authReducer, ...formReducers, routing }); export default (state, action) => rootReducer(action.type === 'USER_LOGOUT' ? undefined : state, action);
Using Redux Toolkit and/or Typescript: const appReducer = combineReducers({ /* your app’s top-level reducers */ }); const rootReducer = ( state: ReturnType<typeof appReducer>, action: AnyAction ) => { /* if you are using RTK, you can import your action and use it's type property instead of the literal definition of the action */ if (action.type === logout.type) { return appReducer(undefined, { type: undefined }); } 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.
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 the 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) }
UPDATE NGRX4 If you are migrating to NGRX 4, you may have noticed from the migration guide that the rootreducer method for combining your reducers has been replaced with the ActionReducerMap method. At first, this new way of doing things might make resetting the state a challenge. It is actually straightforward, yet the way of doing this has changed. This solution is inspired by the meta-reducers API section of the NGRX4 Github docs. First, lets say your are combining your reducers like this using NGRX's new ActionReducerMap option: //index.reducer.ts export const reducers: ActionReducerMap<State> = { auth: fromAuth.reducer, layout: fromLayout.reducer, users: fromUsers.reducer, networks: fromNetworks.reducer, routingDisplay: fromRoutingDisplay.reducer, routing: fromRouting.reducer, routes: fromRoutes.reducer, routesFilter: fromRoutesFilter.reducer, params: fromParams.reducer } Now, let's say you want to reset the state from within app.module //app.module.ts import { IndexReducer } from './index.reducer'; import { StoreModule, ActionReducer, MetaReducer } from '#ngrx/store'; ... export function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function(state, action) { switch (action.type) { case fromAuth.LOGOUT: console.log("logout action"); state = undefined; } return reducer(state, action); } } export const metaReducers: MetaReducer<any>[] = [debug]; #NgModule({ imports: [ ... StoreModule.forRoot(reducers, { metaReducers}), ... ] }) export class AppModule { } And that is basically one way to achieve the same affect with NGRX 4.
My workaround when working with typescript, built on top of Dan Abramov's answer (redux typings make it impossible to pass undefined to reducer as the first argument, so I cache initial root state in a constant): // store export const store: Store<IStoreState> = createStore( rootReducer, storeEnhacer, ) export const initialRootState = { ...store.getState(), } // root reducer const appReducer = combineReducers<IStoreState>(reducers) export const rootReducer = (state: IStoreState, action: IAction<any>) => { if (action.type === "USER_LOGOUT") { return appReducer(initialRootState, action) } return appReducer(state, action) } // auth service class Auth { ... logout() { store.dispatch({type: "USER_LOGOUT"}) } }
Simply have your logout link clear session and refresh the page. No additional code needed for your store. Any time you want to completely reset the state a page refresh is a simple and easily repeatable way to handle it.
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.
Combining Dan Abramov's answer, Ryan Irilli's answer and Rob Moorman's answer, to account for keeping the router state and initializing everything else in the state tree, I ended up with this: const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? { ...appReducer({}, {}), router: state && state.router || {} } : state, action);
I have created actions to clear state. So when I dispatch a logout action creator I dispatch actions to clear state as well. User record action export const clearUserRecord = () => ({ type: CLEAR_USER_RECORD }); Logout action creator export const logoutUser = () => { return dispatch => { dispatch(requestLogout()) dispatch(receiveLogout()) localStorage.removeItem('auth_token') dispatch({ type: 'CLEAR_USER_RECORD' }) } }; Reducer const userRecords = (state = {isFetching: false, userRecord: [], message: ''}, action) => { switch (action.type) { case REQUEST_USER_RECORD: return { ...state, isFetching: true} case RECEIVE_USER_RECORD: return { ...state, isFetching: false, userRecord: action.user_record} case USER_RECORD_ERROR: return { ...state, isFetching: false, message: action.message} case CLEAR_USER_RECORD: return {...state, isFetching: false, message: '', userRecord: []} default: return state } }; I am not sure if this is optimal?
My take to keep Redux from referencing to the same variable of the initial state: // write the default state as a function const defaultOptionsState = () => ({ option1: '', option2: 42, }); const initialState = { options: defaultOptionsState() // invoke it in your initial state }; export default (state = initialState, action) => { switch (action.type) { case RESET_OPTIONS: return { ...state, options: defaultOptionsState() // invoke the default function to reset this part of the state }; default: return state; } };
I've created a component to give Redux the ability to reset state, you just need to use this component to enhance your store and dispatch a specific action.type to trigger reset. The thought of implementation is the same as what Dan Abramov said in their answer. Github: https://github.com/wwayne/redux-reset
The following solution worked for me. I added resetting state function to meta reducers.The key was to use return reducer(undefined, action); to set all reducers to initial state. Returning undefined instead was causing errors due to the fact that the structure of the store has been destroyed. /reducers/index.ts export function resetState(reducer: ActionReducer<State>): ActionReducer<State> { return function (state: State, action: Action): State { switch (action.type) { case AuthActionTypes.Logout: { return reducer(undefined, action); } default: { return reducer(state, action); } } }; } export const metaReducers: MetaReducer<State>[] = [ resetState ]; app.module.ts import { StoreModule } from '#ngrx/store'; import { metaReducers, reducers } from './reducers'; #NgModule({ imports: [ StoreModule.forRoot(reducers, { metaReducers }) ] }) export class AppModule {}
Dan Abramov's answer helped me solve my case. However, I encountered a case where not the entire state had to be cleared. So I did it this way: const combinedReducer = combineReducers({ // my reducers }); const rootReducer = (state, action) => { if (action.type === RESET_REDUX_STATE) { // clear everything but keep the stuff we want to be preserved .. delete state.something; delete state.anotherThing; } return combinedReducer(state, action); } export default rootReducer;
Just an extension to #dan-abramov answer, sometimes we may need to retain certain keys from being reset. const retainKeys = ['appConfig']; const rootReducer = (state, action) => { if (action.type === 'LOGOUT_USER_SUCCESS' && state) { state = !isEmpty(retainKeys) ? pick(state, retainKeys) : undefined; } return appReducer(state, action); };
This approach is very right: Destruct any specific state "NAME" to ignore and keep others. const rootReducer = (state, action) => { if (action.type === 'USER_LOGOUT') { state.NAME = undefined } return appReducer(state, action) }
For me to reset the state to its initial state, I wrote the following code: const appReducers = (state, action) => combineReducers({ reducer1, reducer2, user })( action.type === "LOGOUT" ? undefined : state, action );
I found that Dan Abramov's 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) }
First on initiation of our application the reducer state is fresh and new with default InitialState. We have to add an action that calls on APP inital load to persists default state. While logging out of the application we can simple reAssign the default state and reducer will work just as new. Main APP Container componentDidMount() { this.props.persistReducerState(); } Main APP Reducer const appReducer = combineReducers({ user: userStatusReducer, analysis: analysisReducer, incentives: incentivesReducer }); let defaultState = null; export default (state, action) => { switch (action.type) { case appActions.ON_APP_LOAD: defaultState = defaultState || state; break; case userLoginActions.USER_LOGOUT: state = defaultState; return state; default: break; } return appReducer(state, action); }; On Logout calling action for resetting state function* logoutUser(action) { try { const response = yield call(UserLoginService.logout); yield put(LoginActions.logoutSuccess()); } catch (error) { toast.error(error.message, { position: toast.POSITION.TOP_RIGHT }); } }
One thing Dan Abramov's answer doesn't do is clear the cache for parameterized selectors. If you have a selector like this: export const selectCounter1 = (state: State) => state.counter1; export const selectCounter2 = (state: State) => state.counter2; export const selectTotal = createSelector( selectCounter1, selectCounter2, (counter1, counter2) => counter1 + counter2 ); Then you would have to release them on logout like this: selectTotal.release(); Otherwise, the memoized value for the last call of the selector and the values of the last parameters will still be in memory. Code samples are from the ngrx docs.
A quick and easy option that worked for me was using redux-reset . Which was straightforward and also has some advanced options, for larger apps. Setup in create store import reduxReset from 'redux-reset' // ... const enHanceCreateStore = compose( applyMiddleware(...), reduxReset() // Will use 'RESET' as default action.type to trigger reset )(createStore) const store = enHanceCreateStore(reducers) Dispatch your 'reset' in your logout function store.dispatch({ type: 'RESET' })
Approach with Redux Toolkit: export const createRootReducer = (history: History) => { const rootReducerFn = combineReducers({ auth: authReducer, users: usersReducer, ...allOtherReducers, router: connectRouter(history), }); return (state: Parameters<typeof rootReducerFn>[0], action: Parameters<typeof rootReducerFn>[1]) => rootReducerFn(action.type === appActions.reset.type ? undefined : state, action); };
why don't you just use return module.exports.default() ;) export default (state = {pending: false, error: null}, action = {}) => { switch (action.type) { case "RESET_POST": return module.exports.default(); case "SEND_POST_PENDING": return {...state, pending: true, error: null}; // .... } return state; } Note: make sure you set action default value to {} and you are ok because you don't want to encounter error when you check action.type inside the switch statement.
Another option is to: store.dispatch({type: '##redux/INIT'}) '##redux/INIT' is the action type that redux dispatches automatically when you createStore, so assuming your reducers all have a default already, this would get caught by those and start your state off fresh. It might be considered a private implementation detail of redux, though, so buyer beware...
for me what worked the best is to set the initialState instead of state: const reducer = createReducer(initialState, on(proofActions.cleanAdditionalInsuredState, (state, action) => ({ ...initialState })),
If you want to reset a single reducer For example const initialState = { isLogged: false } //this will be your action export const resetReducer = () => { return { type: "RESET" } } export default (state = initialState, { type, payload }) => { switch (type) { //your actions will come her case "RESET": return { ...initialState } } } //and from your frontend dispatch(resetReducer())