Array gets undefined - React Redux [duplicate] - javascript
This question already has answers here:
JavaScript: Difference between .forEach() and .map()
(17 answers)
Closed 25 days ago.
i am new to React and Redux. Trying to understand the basics and do some simple examples, but i am stuck in this problem for more than one day i can't find the sollution. I imagine that my mistake is a dumb mistake.
The problem is that i can't print the array of users. When debugging, the variable users is loading with all the corrected ids and users, but after executing the <li key={id}>{name}</li> for three times, it comes back to the forEach and gives me this exception: Uncaught TypeError: Cannot read property 'forEach' of undefined, where users is undefined. And i also get an error corresponding to the PropTypes: Invalid prop user of type array supplied to HomePage, expected object
Here is the code:
store/configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
export default store;
reducers/index.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
//import groupReducer from './groupReducer';
export default combineReducers({
user: userReducer
});
reducers/userReducer.js
import { GET_USERS, ADD_USER, DELETE_USER } from '../actions/types';
const initialState = {
users: [
{ id: 1, name: 'brunao'},
{ id: 2, name: 'flavio'},
{ id: 3, name: 'dudu'}
]
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return [
...state
];
default:
return state;
}
}
actions/usersAction.js
import { GET_USERS, ADD_USER, DELETE_USER } from './types';
export const getUsers = () => {
return {
type: GET_USERS
};
};
components/HomePage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUsers } from '../actions/usersActions';
import PropTypes from 'prop-types';
class HomePage extends Component {
componentDidMount() {
this.props.getUsers();
}
render() {
const { users } = this.props.user;
return(
<div>
<h3>Users</h3>
<ul>
{users.forEach(({id, name}) => (
<li key={id}>{name}</li>
))}
</ul>
</div>
);
}
}
HomePage.propTypes = {
getUsers: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
}
const mapStateToProps = (state) => ({
user: state.user
});
export default connect(mapStateToProps, { getUsers })(HomePage);
You are returning a wrong shape of state in your reducer. Your related code:
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return [
...state
];
default:
return state;
}
}
Here, state is an object but you are returning it in an array by spreading it. So, your state gets broken.
Try it like that:
case GET_USERS:
return state;
As #Idan Dagan pointed out in his answer, actually we do not mutate state in our reducers. I just gave this suggestion since you are just playing around to learn Redux and we are returning the original state here, nothing more. But, this is a suitable and better way to return the state:
case GET_USERS:
return { ...state };
Here is a working code: https://codesandbox.io/s/k5ymxwknpv
I also changed forEach with map again as #Idan Dagan suggested. I haden't realized that. forEach is not the suitable method here since actually it does not return anything. You want to map through your arrays in React and render them.
Also, your state name is confusing :) user.users is a little bit weird, you can think a better one maybe.
Edit after comments
Your GET_USERS action actually is being hit, but you are checking it wrong in your code. You are doing:
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return Object.assign({}, state);
default:
return {
users: [
{ id: 1, name: "TEST" },
{ id: 2, name: "TEST1" },
{ id: 3, name: "TEST2" }
]
};
}
}
What happens here? First action of Redux is INIT. This is the initialization of your state. Now, since there is no certain action, your reducer hits the default case and returns the TEST one. Now, your state becomes this TEST data. Then your GET_USERS is hit and you return a new object which merges the state which is the TEST one. Here is the steps:
First, state is the `initialState` -> `INIT` runs and hits the default case
State is now TEST one -> GET_USERS hit and returns the `state` which is TEST one
You see the TEST one.
How can you test your actions? Just put a console.log in your reducer:
export default function(state = initialState, action) {
console.log("state",state);
console.log("action",action);
.....
and see GET_USERS actually is being hit. The other option is instead of returning the merged object with state, try to merge it with initialState or with spread operator return a new object by using initialState:
return return Object.assign({}, initialState);
or
return {...initialState}
Last option provides you a little bit more understanding how my first explanation works. Try to return this for your GET_USERS:
return {...state, users:[...state.users, {id:4, name: "foo"}]};
You will see a users list with TEST data but the last one will be your foo. This explains how you loose your initialState if you return anything beside state in your default case.
Last suggestion, you can debug your Redux development with Redux Dev Tools. It is a great tool and does much more than debugging. You can easily track all your operations for Redux.
I'm not sure what are you trying to do with the new state.
But there is couple of changes that you need to do:
Use map instead of forEach (because you want to return a new array).
At the reducer you can return the state like this:
export default function(state = initialState, action) {
switch (action.type) {
case GET_USERS:
return Object.assign({}, state);
default:
return state;
}
}
as mention in redux docs:
We don't mutate the state. We create a copy with Object.assign().
Related
My app dont recognize a this.props action as a function (React - Redux)
Dont recognize my action as a function. Hello, im setting up a React-Redux application, and i want to centralize the all the set.states of my projects in one unique store.First, i was trying to call an actios that gives me data of an external API (In array form), it worked well in react, and it worked well too in a friends project. The compiler keeps telling me that the action call method is not a function. Thanks Here's the problematic function: /*imports*/ import { connect } from 'react-redux'; import { getPokeList } from '../../actions/pokeList'; componentDidMount = () => { const pokeUrl = `https://pokeapi.co/api/v2/pokemon/`; getPokeInfo(pokeUrl) .then(data => this.props.getPokeList(data.results)) <--- } /* connection */ const mapStateToProps = state => ({ pokeList: state.pokeList.elements, }); export default connect(mapStateToProps, { getPokeList })(List); Heres my action method: const GET_LIST = 'GET_LIST'; export default { GET_LIST, } export const getPokeList = list => ({ type: GET_LIST, list }); And here's my reducer: import pokeList from '../../actions/pokeList' const initialState = { list: [], }; const reducer = (state = initialState, action) => { if (action.type !== null) { switch (action.type) { case pokeList.GET_LIST: return { ...state, list: action.list }; default: return state; } } return state; }; export default reducer; For the moment i dont use the result data in any part of my code. Thanks and excuse my english!
In App.jsx you need to change the Link import to: import List from '../../components/List/List'; After this your reducers will be reached, etc. I made it this far after pulling down your code. I wasn't sure what you were trying to achieve after this, so I left it at that. Ultimately, the issue you encountered is because you have "export default" in your Link component.
Accessing Reducer in container returns undefined
I just wanted to integrate a new Container in my React App, wired it up with Redux and just wanted to see it's all working. It's not however. accessing the reducer via this.props.selection gives me undefined. I don't know why. It does work in other containers, and the reducer has some well-defined initial state. - I'm not sure I see what the difference is here? Am I missing something trivial? import React, { Component } from 'react' import { connect } from 'react-redux'; import {bindActionCreators} from 'redux'; export class AudioPlayer extends Component { constructor(props) { super(props); this.state = { someComponentState : true } } onLog() { console.log("Logging:"); console.log(this.props.selection); // gives me: undefined } render() { return ( <div> <button onClick={()=> this.onLog()}>LOG</button> </div> ) } } function mapStateToProps (state) { return { selection: state.selection }; } export default connect(mapStateToProps)(AudioPlayer); PS: I've simplified this component somewhat, but I think it should still reflect the problem. edit: reducer example people have asked to see the reducer, however, I've tried this with several reducers that are already implemented in the app and are working in other containers, so I don't think this is where the problem lies - but who knows: import { SELECT_ITEM } from '../actions/types'; export default function(state = {}, action) { switch(action.type) { case SELECT_ITEM: return {...state, error:'', selected: true}; } return state; } edit2: mapStateToProps does not seem to be called at all I just tried to do a console.log in mapStateToProps, to see if it's called, and seems that it never is. Nothing is ever logged. What could be the reason for this? function mapStateToProps (state) { console.log("In map function"); console.log(state); return { selection: state.selection, //both return auth: state.auth // undefined }; } I also added another reducer (auth) which works elsewhere in the app, but here returns undefined. edit3: My Root Reducer import { combineReducers } from 'redux'; import { reducer as form } from 'redux-form'; //reducer imports import authReducer from './auth_reducer'; import articlesReducer from './articles_reducer'; import userReducer from './user_reducer'; import currentSelectionReducer from './currentSelection_reducer'; const rootReducer = combineReducers({ auth: authReducer, user: userReducer, articles: articlesReducer, selection: currentSelectionReducer, }); export default rootReducer;
Can you try removing 'export' from 'export class AudioPlayer extends Component' you can also check this: mapStateToProps not getting called at all
your component code is fine. In your reducer it should be export default function(state = { selected: false }, action) { Further reading: https://redux.js.org/recipes/structuringreducers/initializingstate https://stackoverflow.com/a/37823335/2477619
1) In your debugging please check it enters the exact case in the reducer, that it understands the action.type == SELECT_ITEM, and returns the new state. 2) Also notice selection is an object, which contain the 'selected' inside it. Your 'selection' reducer contains: {...state, error:'', selected: true} maybe there is a confusion about this?
Map state to props not updating after Redux
My Redux Store is correctly being updated which can be seen using React Native Debugger. However, the props inside my component are not updating and are undefined. In my component below you can see I have correctly mapped to the "sessionModerator" reducer. I have verified this and can see the prop when consoling this.props. Component: const mapStateToProps = state => { return { session: state.screenReducers.session, list: state.screenReducers.sessionList, sessionUser: state.screenReducers.sessionUser, user: state.sharedReducers.user, sessionListItem: state.screenReducers.sessionListItem, sessionSortOrder: state.sharedReducers.sessionSortOrder, sessionModerator: state.sharedReducers.sessionModerator }; }; My reducer is added as seen below: Reducers Index file: import { reducer as sessionModerator } from './session/reducers/session-moderator'; export const reducers = combineReducers({ sessionModerator: sessionModerator, }); Actions File: import Types from '../../../types'; export const start = () => { return { type: Types.TYPES_SESSION_MODERATOR_START, payload: true }; }; export const stop = () => { return { type: Types.TYPES_SESSION_MODERATOR_STOP, payload: false }; }; Reducers File: import Types from '../../../types'; export const reducer = (state = false, action) => { switch (action.type) { case Types.TYPES_SESSION_MODERATOR_START: return action.payload; case Types.TYPES_SESSION_MODERATOR_STOP: return action.payload; default: return state; } }; In the below image you can see that the store is updated as the value for sessionModerator is set to "true", but the console of the actual props during the operation is undefined. What I have tried: I have tried various things mostly revolving around the structure of my state, for example, I tried adding the boolean inside an actual object and updating the value as an object property but that didn't seem to work. I feel like I am not updating the boolean correctly but haven't been able to figure it out. Any help would be greatly appreciated. Thank you.
sessionModerator is in screenReducers in the debugger not in sharedReducers as in your mapStateToProps. Try this one: const mapStateToProps = state => { return { session: state.screenReducers.session, list: state.screenReducers.sessionList, sessionUser: state.screenReducers.sessionUser, user: state.sharedReducers.user, sessionListItem: state.screenReducers.sessionListItem, sessionSortOrder: state.sharedReducers.sessionSortOrder, sessionModerator: state.screenReducers.sessionModerator }; };
Redux Fetch JSON Data
my Redux fetch is returning empty.. It does not break but it just returns me empty object. Here is the code for my action (newsActions.js): import axios from 'axios'; import kickstarterData from '../server/kickstarter-october.json'; export const FETCH_KICKSTARTER = 'FETCH_KICKSTARTER'; export function fetchKickstarter() { return { type: FETCH_KICKSTARTER, payload: { data: kickstarterData } }; } Here is my Reducer: import { FETCH_KICKSTARTER } from '../actions/kickstarterActions'; export default function(state = [], action) { switch (action.type) { case FETCH_KICKSTARTER: debugger; return [action.payload.data, ...state]; } return state; }; https://stackoverflow.com/questions/ask# Here is my index.js that combines all the reducers: import { combineReducers } from 'redux'; import NewsReducer from './reducer_news'; import KickstarterReducer from './reducer_kickstarter'; const rootReducer = combineReducers({ news: NewsReducer, kickstarters: KickstarterReducer }); export default rootReducer; Finally, inside my app.js I have the following code: const mapStateToProps = (state) => ({ news: state.news, kickstarters: state.kickstarters }); export default connect(mapStateToProps, {...newsActions, ...kickstarterActions})(App); Could anyone tell me why this is breaking? Also, could anyone suggest me a better/cleaner way of writing these codes? Thank you
I have a hunch that in your reducer return [action.payload.data, ...state]; should be return [...action.payload.data, ...state]; Frankly it should be just return [...action.payload.data ]; Since i don't have any idea about your biz logic, but later seems more correct to me (why do you need to merge it with old state). you need to spread the action.payload.data in the state.
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?
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())