Open connercms opened 6 years ago
Very similar setup for me as regards app, configureStore, and rootReducer - the only exception being that I have nested persistReducers.
Same problem, I clear the state on a logout action, see it cleared by the action, but the cleared state isn't persisted. Logging in as a different user or refreshing/re-opening the page rehydrates the pre-cleared state.
@DJTB As a workaround, inside my action creator I dispatch an object with type 'LOG_OUT', then in every single reducer file I have a case for 'LOG_OUT' and set state: initialState. It seems to persist this just fine. I used the code I posted above before I updated redux persist versions and it worked fine - not sure what changed
I had an inkling I might have to clear explicitly within each nested reducer, though that's kind of a pain even with a reducer decorator. Thanks for the info - I'll give it a shot.
I've tried various combinations of clearing state, and I often still get back stale state / or new state isn't persisted.
I see that clearing from the top level removes the _persist
info from the state inside my nested reducers, and that completely messes up redux-persist?
@DJTB how are you clearing state? like with an action or via persistor.purge
or something else?
I have the same problem here. Does anybody solve an issue?
@DavitVosk can you elaborate on what the issue exactly is?
@rt2zz Thanks for your response. So when I press Logout button, I call 2 actions with respective types: USER_LOGOUT and RESET. Based on the first type I return state=undefined in appReducer (as mentioned in this issue raise description). The second type RESET I use to reset the state (I use redux-reset). The outcome: when Logout is pressed, the app navigates to the welcome screen of the app (as it should be), BUT when I reload the app it goes to the first page of the app, namely I could not somehow nullify the redux state when logged out.
ah ok so I am fairly certain there is an issue with setting state to undefined. We should add a test for this, I am not sure what the behavior will be.
In general redux persist assumes state is an object, ie not undefined. you might try something like setting state to an empty object on logout instead of undefined.
@rt2zz I tried with setting empty object to state when action type is USER_LOGOUT, but unfortunately no success.
The eventual solution I settled on (less than ideal) was to remove top level persistence and use nested persistors only, which seems to work for my case.
const rootReducer = combineReducers({
notPersisted: someReducer,
nestedPersisted: persistReducer(specificNestedPersistConfig, nestedReducer),
/* more nested / unpersisted reducers */
});
Each nested persisted reducer shares a basic persist config ({ storage, version, debounce }
), but merges its own specific key
and whitelist.
The nested reducers all have their own initialState
declared within the file.
And they all have a final switch case for USER_LOGOUT
: (state) => initialState
Sometimes initialState has keys/values, sometimes it's just {}
but never undefined.
Localforage indexedDB ends up with:
I'd prefer a single USER_LOGOUT
at the top level (and a single DB entry / key), but I could never get it to work properly since it seemed to mess with the _persist
entries in redux state which I think is what was causing the problems. @rt2zz I was clearing state at the top level exactly the same as the OP before changing to nested with their own initialStates.
@DJTB ah thanks for clarity. So yes it probably does confuse the persist if you blow out state from the top. I would like to allow this however.
One probable solution is to store _persist state outside of the reducer. This seemingly opens up a few more use cases. There is an open issue for this somewhere that I cannot find atm. Not without challenges though, we would need to be very careful about out of sync state issues and possible performance impact.
Not sure what the timeline for such a change might be.
@DJTB yes I could not though find a way to handle logout at the top level and without making each reducer to listening to my "USER_LOGOUT" action type. I also tried your above-mentioned structure with nestedPersisted reducers, but it was not working from my side. I just make sure all reducers listen to my USER_LOGOUT action type and nullify themself to their initial state. It works like a charm. @DJTB and @rt2zz Guys thanks for being ready to discuss and help :)
But I think this issue should not be closed since the bug is still there. We just found an alternative solution.
I have this issues as below: In Android, I listening event AppState for updating data.
componentWillUnmount(){
AppState.removeEventListener('change', this.handleAppStateChange.bind(this));
}
handleAppStateChange (nextAppState) {
if(nextAppState == 'background') {
session.logout().then(() => {});
}
}
session.logout(): it will remove data in store I have 2 case:
Just ran into this today. Is there any other solution other than each reducer listening to the log out action?
I also have "CLEAR_APP" action type at every reducer. This action clears everything needed manually, all other solutions didn't not worked as expected.
You can listen on actions at the root reducer, instead of checking for a log out at every reducer you have. However, I still have the problem of the state rehydrating when the application closes without invoking log out action.
I'm so lost. I cannot seem to figure out to fit in the custom rootReducer (Dan's stack example). I even tried to mimic the example you gave directly at it breaks my app:
const reducers = persistCombineReducers(persistConfig, myReducers)
const appReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
storage.removeItem('persist:root')
state = undefined
}
return reducers
}
const store = compose(applyMiddleware(...middleware))(createStore)(appReducer)
console.log(store.getState())
const persistor = persistStore(store)
return { store, persistor }
The store returns as a function and not an object. I've tried multiple ways with no luck. For instance this seemed the most logical to me:
const rootReducer = combineReducers(myReducers)
const persistedReducer = persistReducer(persistConfig, rootReducer)
const appReducer = (state, action) => {
if (action.type === 'USER_LOGOUT') {
storage.removeItem('persist:root')
state = undefined
}
return persistedReducer
}
const store = createStore(appReducer, enhancer)
const persistor = persistStore(store)
return { store, persistor }
But that also returns a function and not the store. But if I run
const store = createStore(persistedReducer, enhancer)
instead of
const store = createStore(appReducer, enhancer)
It returns the store fine, but the custom reducer needed to reset the store is left out.
Also I've seen no one mention the use of storage.removeItem('persist:root')
which Dan Ab. specified in his stack if you're using redux-persist
@navata @anguyen1817 @acheronte did you found a fix for the AppState bug?
componentWillUnmount(){
AppState.removeEventListener('change', this.handleAppStateChange.bind(this));
}
handleAppStateChange (nextAppState) {
if(nextAppState == 'background') {
session.logout().then(() => {});
}
}
If you have a rootPersistConf object, then you can add a blacklist array that takes strings of your modules to prevent certain modules from being persisted. the modules revert back to the initial state.
@mgarnick2000 This is not exactly about blacklisting.. The idea is to be able to reset whole store to initial value on user logout without having case USER_LOGGED_OUT: return initialState
in every reducer. It's not about not storing some parts of the reducer hierarchy.
I faced the simlar problem. If i use undefined to reset the root state, new state will never be persisted. I find that the reason is here
I am facing the same issue
Same issue here. Setting state = undefined from root reducer stops data from being persisted until the app is restarted.
I've found a working semi-hacky solution for my setup with nested persistance configs and a top-level RESET_STORE action. As it seems that several different problems are discussed in this thread, and because I don't want to make this post too long or diluted, I will just document my specific working solution instead of trying to apeal to certain problems discussed here.
I initialize my store with a rootReducer such as in "Dan's stack example", with a persistance config (I use localforage as storage):
const rootPersistConfig = {
key: 'root',
storage: localforage,
blacklist: ['aReducer']
};
const store = createStore(
persistReducer(rootPersistConfig, rootReducer),
composeEnhancers(...)
);
The root reducer accepts actions just as in "Dan's stack example".
export function rootReducer(state, action) {
if (action.type === RESET_STORE) {
state = undefined;
}
return appReducer(state, action);
}
This setup allows me to call RESET_STORE, and have all the reducers reset to their initial state (by common reducer practice), without tampering with the root persistance object which controls persistance for the whole app.
However, all nested persistance configurations in appReducer will suffer from the RESET_STORE action by having their persistance object nullified. This stops their data from being persisted until the next refresh. A nested persistance configuration could look like this:
const aReducerNestedPersistanceConfig = {
key: 'aReducer',
storage: localforage,
whitelist: ['keepOnlyThisValuePersisted']
};
const appReducer = combineReducers({
aReducer: persistReducer(aReducerNestedPersistanceConfig, aReducer)
aSecondReducer,
aThirdReducer
});
This leads to my solution for keeping the persistance on nested persistance configs.. By adding a RESET_STORE case to just the reducers with nested persistance configs, it is possible to keep the persistance object from being nullified by copying it and just placing it in the store.
// Inside aReducer switch-case
case RESET_STORE:
return {
...state,
_persist: {
version: -1,
rehydrated: true
}
};
...
PS: I have not had any use for localforage.removeItem('persist:root') because the localforage storage updates (to a pretty much empty store) when the store is reset.
The eventual solution I settled on (less than ideal) was to remove top level persistence and use nested persistors only, which seems to work for my case.
const rootReducer = combineReducers({ notPersisted: someReducer, nestedPersisted: persistReducer(specificNestedPersistConfig, nestedReducer), /* more nested / unpersisted reducers */ });
Each nested persisted reducer shares a basic persist config (
{ storage, version, debounce }
), but merges its own specifickey
and whitelist.The nested reducers all have their own
initialState
declared within the file. And they all have a final switch case forUSER_LOGOUT
:(state) => initialState
Sometimes initialState has keys/values, sometimes it's just
{}
but never undefined.Localforage indexedDB ends up with:
I'd prefer a single
USER_LOGOUT
at the top level (and a single DB entry / key), but I could never get it to work properly since it seemed to mess with the_persist
entries in redux state which I think is what was causing the problems. @rt2zz I was clearing state at the top level exactly the same as the OP before changing to nested with their own initialStates.
This solution works for me Thanks
this is what I've do `const rootReducer = (state: any, action: any) => { if (action.type === 'logout') { purgeStoredState(config); return reducers(undefined, action); }
return reducers(state, action); };
export const STORE_GENERATOR = () => { const store = createStore( rootReducer, composeWithDevTools(applyMiddleware(...middlewares)), );
const persistor = persistStore(store); return {store, persistor}; }; `
This bug is happening for me too and it's shame to see it not fixed after more than 4 years since this issue has been opened!
I'm using persistor.purge()
upon logout like this:
// Show logout message and purge the store
useEffect(() => {
if (isLoggedOut) {
Toast.show({
type: 'success',
text1: logoutMessage,
});
persistor.purge();
}
}, [isLoggedOut]);
Everything works correctly but after closing and reopening the app, the previous state get rehydrated with the persist/REHYDRATE
action and then upon seeing that isLoggedOut
is true
, persistor.purge()
gets called again:
A few of you have correctly (I think) pointed out that clearing out the state from the top level also clears the _persist field in the nested reducer, which seems to be causing the issue. My solution is to simply run persistor.purge
right before my top level action that clears the store (or resets it to its initial state).
persistor.purge();
dispatch({ type: LOGOUT });
I could argue that this is still a workaround and not a solution to the problem, but it seems totally adequate to me.
I'm still having this issue.
I followed Dan Abramov's example on clearing a redux store by dispatching an action that gets handled by a root reducer. My code for that is here
This works fine. I have a logout function that dispatches action with type 'CLEAR_DATA' and I see in redux logger that the action does indeed clear the state. However, if I then close the application completely and then reopen it, it opens and all the data is somehow rehydrated. I am unsure if I possibly just have redux persist set up incorrectly. My code to set up redux persist is below
index.js
App.js
configureStore.js
My package versions "react-native": "0.51.0", "react-redux": "^5.0.6", "redux": "^3.7.2", "redux-persist": "^5.4.0",