rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.97k stars 867 forks source link

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

Open larissathasdefar opened 6 years ago

larissathasdefar commented 6 years ago

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)
tylermurry commented 6 years ago

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

larissathasdefar commented 6 years ago

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
)
tkow commented 6 years ago

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

tkow commented 6 years ago

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.

ghost commented 5 years ago

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);
};
orpheus commented 4 years ago

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

Red-Sa commented 2 years ago

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