redux-persist icon indicating copy to clipboard operation
redux-persist copied to clipboard

Unexpected key "_persist" found in previous state received by the reducer.

Open larissathasdefar opened this issue 7 years ago • 7 comments

Hello,

To reset my store when an user logout, I merge my state with the initial state of my application in redux.

When I login, then logout, login again and then logout, I get the following error: Unexpected key "_persist" found in previous state received by the reducer.

Debugging it, I found out that when I do the second logout action, my variable initialState is mutated, being returned with and extra prop "_persist" in the json, and because the state doesn't have "_persist", it triggers the error.

I don't know if I am doing something wrong or if it's an error in persist-redux.

I am working with react-native, expo, react-navigation, redux and redux-thunk.

redux/index.js

const appReducer = combineReducers({
    login: loginReducer,
    recover: recoverReducer
})

export default (state = initialState, action) => appReducer(
    action.type === 'RESET_STORE'
        ? initialState
        : state,
    action
)

initialState

export default {
    login: {
        errorMessage: '',
        hasError: false,
        isLogged: false,
        loading: false
    },
    recover: {
        loading: false,
        hasError: false
    }
}

store.js

const persistConfig = {
    key: 'root',
    storage
}

const persistedReducer = persistReducer(persistConfig, reducers)

export const store = createStore(
    persistedReducer,
    initialState,
    applyMiddleware(thunk)
)

export const persistor = persistStore(store)

larissathasdefar avatar Jun 18 '18 14:06 larissathasdefar

I have this exact same scenario. We're you able to find a solution?

tylermurry avatar Jul 16 '18 20:07 tylermurry

I am using a workaround with dissoc from ramda, manually removing _persist from my object.

const formatInitialState = () => dissoc('_persist', initialState)

export default (state = initialState, action) => appReducer(
    action.type === 'RESET_STORE'
        ? formatInitialState()
        : state,
    action
)

larissathasdefar avatar Jul 16 '18 21:07 larissathasdefar

We have met same problem.I wonder why we success reset state only at first. I have trouble to comprehend how this occur.

tkow avatar Oct 20 '18 14:10 tkow

I found redux-persist add _persist prop to returned object.So when we return initialState as reducer return value, initialState is mutated and their prop info is cached because it has ref. we can also avoid this by returning copy of initialState.

tkow avatar Oct 20 '18 14:10 tkow

Thanks @tkow , return a copy works well:

const rootReducers = (state, action) => {
  if (action.type === RESET_STORE) {
    Object.keys(state).forEach(key => {
      storage.removeItem(`persist:${key}`);
    });

    state = Object.assign({}, initialState);
  }

  return reducers(state, action);
};

ghost avatar Feb 08 '19 17:02 ghost

This error pops up after I run flush(). Anyone else experience this?

orpheus avatar Jun 23 '20 15:06 orpheus

I am using a workaround with dissoc from ramda, manually removing _persist from my object.

const formatInitialState = () => dissoc('_persist', initialState)

export default (state = initialState, action) => appReducer(
    action.type === 'RESET_STORE'
        ? formatInitialState()
        : state,
    action
)

@larissathasdefar I have a better solution to solve this problem without any third party library and without needing to import the initial state of each reducer, which is a real problem in case you have a big application with a lot of reducers.

import {user} from './userReducer';
import {post} from './postReducer';
...
...
//end imports
const appReducer = combineReducers({ user: userReducer, post... }) //<<  combine all reducers 

const rootReducer = (state, action) => {
	if (action.type === HYDRATE) {  
     // For me on Nextjs the action type is HYDRATE not RESET_STORE
    // clear storage as @ghost suggested 
		Object.keys(state).forEach((key) => {
			storage.removeItem(`persist:${key}`);
		});
              // now destructor the returned action.payload object and get rig of _persist key
		state = (({ _persist, ...rest }) => rest)(action.payload);
	}

	return appReducer(state, action);
}; 
export default rootReducer  

I hope this will help anyone getting the same issue

Red-Sa avatar Mar 23 '22 07:03 Red-Sa