React Redux Persist is rehydrating, but no stored data is retrieved, instead, default - javascript

I've been struggling with this problem for two days, so I need help!
I have a React web in which I've added Redux Persist (also Redux Saga for handling requests) as the documentation said.
I'm testing with a store that doesn't have any saga in the middle, when I trigger an action, the data is updated, I can see it in the Debugger, but when I refresh, despite the Redux Hydrate process runs, that value goes back to the default one.
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga'
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import rootReducer from './reducers';
// import rootSaga from './sagas';
const persistConfig = {
key: 'root',
storage: storage,
// whitelist: ['login', 'account', 'layout'],
stateReconciler: autoMergeLevel2, // see "Merge Process" section for details.
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(persistedReducer, composeEnhancers(applyMiddleware(sagaMiddleware)));
const persistor = persistStore(store);
export { store, persistor, sagaMiddleware };
reducers.js
import { combineReducers } from 'redux';
// Front
import layout from './layout/reducer';
// Authentication Module
import account from './auth/register/reducer';
import login from './auth/login/reducer';
import forget from './auth/forgetpwd/reducer';
const rootReducer = combineReducers({
layout,
account,
login,
forget
});
export default rootReducer;
reducer.js (the one I'm testing, Layout)
import { ACTIVATE_AUTH_LAYOUT, ACTIVATE_NON_AUTH_LAYOUT, TOGGLE, TOGGLE_LD } from './actionTypes';
const initialState={
topbar:true,
sidebar:true,
footer:true,
is_toggle : true,
is_light : true
}
const layout = (state=initialState,action) => {
switch(action.type){
case ACTIVATE_AUTH_LAYOUT:
state = {
...state,
...action.payload
}
break;
case ACTIVATE_NON_AUTH_LAYOUT:
state = {
...state,
...action.payload
}
break;
case TOGGLE:
state = {
...state,
is_toggle : action.payload
}
break;
case TOGGLE_LD:
state = {
...state,
is_light : action.payload
}
break;
default:
// state = state;
break;
}
return state;
}
export default layout;
actions.js
import { ACTIVATE_AUTH_LAYOUT, ACTIVATE_NON_AUTH_LAYOUT, TOGGLE, TOGGLE_LD } from './actionTypes';
export const activateAuthLayout = () => {
return {
type: ACTIVATE_AUTH_LAYOUT,
payload: {
topbar: true,
sidebar: true,
footer: true,
rodri: 'butta',
layoutType: 'Auth'
}
}
}
export const activateNonAuthLayout = () => {
return {
type: ACTIVATE_NON_AUTH_LAYOUT,
payload: {
topbar: false,
sidebar: false,
footer: false,
layoutType: 'NonAuth'
}
}
}
export const toggleSidebar = (is_toggle) => {
return {
type: TOGGLE,
payload: is_toggle
}
}
export const toggleLightDark = (is_light) => {
return {
type: TOGGLE_LD,
payload: is_light
}
}
index.js (APP)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';
import rootSaga from './store/sagas';
import {persistor, store, sagaMiddleware} from './store';
sagaMiddleware.run(rootSaga);
const app = (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
);
ReactDOM.render(app, document.getElementById('root'));
serviceWorker.unregister();
And fragments of the class
...
this.props.toggleSidebar(!this.props.is_toggle);
...
const mapStatetoProps = state => {
const { is_toggle,is_light } = state.layout;
return { is_toggle,is_light };
}
export default withRouter(connect(mapStatetoProps, { toggleSidebar })(Topbar));
The debugger
First time run, when I toggle the menu bar so the action gets triggered)
After refreshing the browser, the rehydrate should bring the layout -> is_toggle to false.. but it remains true (as default)
Fragments in color:

I think your issue is how you are setting up your store:
const store = createStore(persistedReducer, composeEnhancers(applyMiddleware(sagaMiddleware)));
The structure of this function is createStore(reducer, [preloadedState], [enhancer])
So you are trying to pass your enhancers as preloadedState.
Try changing it to this:
const store = createStore(persistedReducer, {}, composeEnhancers(applyMiddleware(sagaMiddleware)));
Also, when you setup your persistConfig I see that you have the whitelist commented out. Any reducer state you want to keep needs to be in this list so it should not be commented out:
// whitelist: ['login', 'account', 'layout'], // uncomment this
And finally as a side note, you don't need break in all of your switch cases

I faced the same bug, the thing worked for me was in the comments of the Author's question saying:
And this solution didn't worked for me :( https://github.com/rt2zz/redux-persist/issues/1114#issuecomment-549107922
So if anyone stuck on this issue then could try this solution out at: https://github.com/rt2zz/redux-persist/issues/1114#issuecomment-549107922
Issue: My Persist was being called first before rehydrate, making my states to the default state again. Hence everytime i refresh my states were gone from the local storage.

Related

Unable to access REDUX State in multple Pages and Components

I need some help figuring this issue out. After setting up Redux store and reducer in my app, I was able to successfully log and render updated state upon click in one, but not multiple pages. Below are steps and code sample:
Step1:
I installed Redux and wrapped the store around my entire app
// _app.js
import Layout from '../components/Layout';
import { SessionProvider } from 'next-auth/react';
import store from '../store';
import { Provider } from 'react-redux';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<>
<Provider store={store}>
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
<ToastContainer />
</Layout>
</SessionProvider>
</Provider>
</>
);
}
export default MyApp;
Step 2:
Setup an instance of a slice, Store and reducer
// mySlice.js
import { createSlice} from '#reduxjs/toolkit';
const initialState = {
user: {
role: ""
},
};
export const userStatusSlice = createSlice({
name: 'userStatus',
initialState,
reducers: {
userInfo: (state, action) => {
state.user.role = action.payload.role; // only this value comes from payload onClick
},
},
});
// Action creators are generated for each case reducer function
export const { userInfo } = userStatusSlice.actions;
export default userStatusSlice.reducer;
Step 3: Store...
//store.js
import { configureStore } from '#reduxjs/toolkit';
import userStatusSlice from './slices/userSlice/userStatus';
export default configureStore({
reducer: {
userStatus: userStatusSlice,
},
});
Step 4: Setup pages and React Hook useSelector, and tried accessing dispatched actions set as state variables in multiple pages. On one page I was able to fetch the data successfully, but not on the other page(s)
//First Page
import { useSession, getSession } from 'next-auth/react';
import { useSelector } from 'react-redux';
const firstPage = () => {
const { data: session } = useSession();
const { role } = useSelector((state) => state.userStatus.user);
console.log(role); // There is role successfully logged to the console
return (
<>
</>
);
};
export default firstPage;
//Second page.js
import { useSession } from 'next-auth/react';
import { useSelector } from 'react-redux';
const secondPage = () => {
const { data: session } = useSession();
const { role } = useSelector((state) => state.userStatus.user);
console.log(role) // There is NO role - why?
return (
<>
</>
);
};
export default secondPage;
I appreciate all input to help resolving this issue. Thanks in advance

Redux Toolkit - Save dark mode value to local storage

I'm currently learning redux toolkit and I want to store dark mode state in localStorage so that if the users refresh the page, selected mode stays the same. My application looks like this:
index.js
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { configureStore } from "#reduxjs/toolkit";
import darkModeReducer from "./darkModeSlice";
import App from "./App";
const store = configureStore({
reducer: {
darkMode: darkModeReducer,
},
});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
darkModeSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
darkMode: false,
};
export const darkModeSlice = createSlice({
name: "darkMode",
initialState,
reducers: {
toggleDarkMode: (state) => {
state.darkMode = !state.darkMode;
},
},
});
export const { toggleDarkMode } = darkModeSlice.actions;
export default darkModeSlice.reducer;
What would be the optimal solution for this? Please note that I only want to store the dark mode state in local storage, so if I add other reducers in the future their values ​​won't be automatically saved there.
You could change your initialState in darkModeSlice
const initialState = {
darkMode: localStorage.getItem("darkMode") || false
}
If localstorage is undefined, it will automatically be false.
And when user changes darkMode you store it with :
localStorage.setItem("darkMode", darkMode);

Logout React Reducer w/ History, State & Action Bug

Hey I've looked at a bunch of help files now and cant seem to get the issue solved. Most suggestions are using a different setup than I have. The main issue that the others dont have is the LOGOUT feature. Can you suggest another way to handle the LOGOUT?
Here is my index.js for "combine reducers":
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import { LOGOUT_USER } from '../constants/index';
/* App Reducer Files */
import app from './app/reducer';
import accountData from './account/reducer';
import employeeData from './employee/reducer';
import locationData from './location/reducer';
import googleData from './google/reducer';
import requestData from './request/reducer';
import menuItemData from './menuItem/reducer';
import orderData from './order/reducer';
/* Public Reducer Files */
import valorData from './valor/reducer';
const appReducer = history => combineReducers({
router: connectRouter(history),
app,
accountData,
employeeData,
locationData,
googleData,
requestData,
menuItemData,
orderData,
// Public
valorData,
});
const rootReducer = history => (state, action) => {
if (action.type === LOGOUT_USER) {
state = undefined;
}
return appReducer(history, state, action);
};
export default rootReducer;
And my store.js:
import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { routerMiddleware } from 'connected-react-router';
import rootReducer from '../services';
import history from '../history';
const debugware = [];
if (process.env.NODE_ENV !== 'production') {
debugware.push(createLogger({
collapsed: true,
}));
}
export default function configureStore(initialState) {
const store = createStore(
rootReducer(history),
initialState,
compose(
applyMiddleware(
routerMiddleware(history),
thunkMiddleware,
...debugware,
),
),
);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../services', () => {
const nextRootReducer = require('../services/index').default;
store.replaceReducer(nextRootReducer);
});
}
return store;
}
This was working before I upgraded to a new router version. Again the main issue is the LOGOUT. If I just export appReducer it works just fine but doesnt logout.
WOW. The moment I post it I try one more thing...
const rootReducer = history => (state, action) => {
if (action.type === LOGOUT_USER) {
state = undefined;
}
return appReducer(history)(state, action);
};
Why are you returning an undefined state? Instead, return
if (action.type === LOGOUT_USER) {
return{ ...state, user: [] }
}
It basically returns and empty array as user.

Redux-Saga somehow reducer updates without going through the saga

I am new to the saga world. Although I have worked with thunk on react-native territory, I am very confused at the moment. I am trying to get the skeleton of my project going which I expect to get very large soon. With that in mind, I am trying to separate the logic into multiple files.
I have gotten the reducer to fire except it is not the way I want. I am not sure how it is even happening. My saga does not fire but my state updates. I see the console log from my reducer but nothing from the saga watcher function. What should I change?
Index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { Provider } from 'react-redux'
import reducer from './reducers'
import rootSaga from './sagas'
import App from './App'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("etlRootDiv"),
);
App.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import FileExtensionSelector from './components/FileExtensionSelector'
import { setFileExtension } from './actions'
class App extends Component {
constructor(props) {
super(props)
}
handleTypeSelect() {
console.log('handle more click');
this.props.setFileExtension('zip');
console.log(this.props);
}
componentWillReceiveProps(nextProps){
console.log(nextProps);
}
render() {
return (
<div>
<FileExtensionSelector onFileTypeSelect={this.handleTypeSelect.bind(this)} />
<div>{this.props.fileType} ...asdasd</div>
</div>
)
}
}
const mapStateToProps = ({ metaState }) => {
const { fileType } = metaState;
return { fileType };
};
const mapDispatchToProps = (dispatch) => ({
setFileExtension(ext) {
dispatch(setFileExtension(ext))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
reducers/index.js
import { combineReducers } from 'redux';
import metaState from './MetaStateReducer';
const rootReducer = combineReducers({
metaState,
})
export default rootReducer
reducers/metastatereducer.js
const INITIAL_STATE = {
fileType: null,
hasHeader: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case 'SET_FILE_EXTENSION':
console.log('/// in set file reducer ///');
console.log(action);
// console.log({ ...state, ...INITIAL_STATE, fileType: action.payload });
return { ...state,...INITIAL_STATE, fileType: action.payload };
default:
return state;
}
}
actions/metaStateActions.js
function action(type, payload = {}) {
return { type, ...payload }
}
export const SET_FILE_EXTENSION = "SET_FILE_EXTENSION";
export const setFileExtension = (extension) => action( SET_FILE_EXTENSION, { payload: extension });
actions/index.js
export { setFileExtension, SET_FILE_EXTENSION } from './metaDataActions';
sagas/metastatesagas.js
import { take, put } from 'redux-saga/effects'
import { SET_FILE_EXTENSION } from '../actions';
function* watchFileExtension(ext) {
console.log(' --- in watch file ext ---');
const { extension } = yield take(SET_FILE_EXTENSION)
console.log(`set extension is ${extension}`);
// yield put({ type: 'SET_FILE_EXTENSION', payload: ext });
}
export const metaStateSagas = [
take("SET_FILE_EXTENSION", watchFileExtension),
]
sagas/index
import { all } from 'redux-saga/effects'
import { metaStateSagas } from './MetaStateSagas';
export default function* rootSaga() {
yield all([
...metaStateSagas,
])
}
redux-saga always passes an action along to the store before attempting to process itself. So, the reducers will always run before any saga behavior executes.
I think the error is that your metaStateSagas array needs to use takeEvery, not take, but I'm not entirely sure. Try that and see if it fixes things.

preloadedState - session result overwritten by another reducer

I have a session reducer (using the redux-session library) which uses middleware to recover the state from the localstore. I can see from debugging tools that this is working as intended, however it is being replaced by my user reducer's initial state.
I feel I should be using preloadedState, but I cant get the result of a reducer into createStore?
storedState is being restored correctly (I can log it into console).
session: {user: {data: bhBSh}}, user: {data: null}
I cant see the best way to copy 'session' back to 'user' when the page is reloaded?
Session reducer:
function sessionReducer (state = {}, action) {
switch (action.type) {
case 'LOAD_STORED_STATE':
console.log(action.storedState); //Working!!!!!
return action.storedState;
default:
return state;
}
}
User reducer:
import { fromJS } from 'immutable';
import {
USER_LOGGING_IN,
USER_LOGGED_IN,
USER_LOGGED_OUT,
} from '../../constants';
const userInitialState = fromJS({
data: null,
isLoading: false,
});
function userReducer(state = userInitialState, action) {
switch (action.type) {
case USER_LOGGING_IN:
return state
.set('isLoading', true);
case USER_LOGGED_IN:
return state
.set('data', action.payload)
.set('isLoading', false);
case USER_LOGGED_OUT:
return userInitialState;
default:
return state;
}
}
export default userReducer;
export default function createReducer(injectedReducers) {
return combineReducers({
session: sessionReducer,
user: userReducer,
...injectedReducers,
});
}
configureStore:
export default function configureStore(history, session) {
session,
routerMiddleware(history),
thunkMiddleware
];
const enhancers = [
applyMiddleware(...middlewares),
];
//compose enhancers removed for readability
const store = createStore(
createReducer(),
//preloaded state??
composeEnhancers(...enhancers)
);
store.injectedReducers = {}; // Reducer registry
return store;
}
app.js
/**
* app.js
*
* This is the entry file for the application, only setup and boilerplate
* code.
*/
// Needed for redux-saga es6 generator support
import 'babel-polyfill';
// Import all the third party stuff
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import FontFaceObserver from 'fontfaceobserver';
import createHistory from 'history/createBrowserHistory';
import { createSession } from 'redux-session';
import 'sanitize.css/sanitize.css';
// Import root app
import App from 'containers/App';
// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';
// Load the favicon, the manifest.json file and the .htaccess file
/* eslint-disable import/no-webpack-loader-syntax */
import '!file-loader?name=[name].[ext]!./images/favicon.ico';
import '!file-loader?name=[name].[ext]!./images/icon-72x72.png';
import '!file-loader?name=[name].[ext]!./images/icon-96x96.png';
import '!file-loader?name=[name].[ext]!./images/icon-120x120.png';
import '!file-loader?name=[name].[ext]!./images/icon-128x128.png';
import '!file-loader?name=[name].[ext]!./images/icon-144x144.png';
import '!file-loader?name=[name].[ext]!./images/icon-152x152.png';
import '!file-loader?name=[name].[ext]!./images/icon-167x167.png';
import '!file-loader?name=[name].[ext]!./images/icon-180x180.png';
import '!file-loader?name=[name].[ext]!./images/icon-192x192.png';
import '!file-loader?name=[name].[ext]!./images/icon-384x384.png';
import '!file-loader?name=[name].[ext]!./images/icon-512x512.png';
import '!file-loader?name=[name].[ext]!./manifest.json';
import 'file-loader?name=[name].[ext]!./.htaccess'; // eslint-disable-line import/extensions
/* eslint-enable import/no-webpack-loader-syntax */
import configureStore from './configureStore';
// Import i18n messages
import { translationMessages } from './i18n';
// Import CSS reset and Global Styles
import './global-styles';
// Observe loading of Open Sans (to remove open sans, remove the <link> tag in
// the index.html file and this observer)
const openSansObserver = new FontFaceObserver('Open Sans', {});
// When Open Sans is loaded, add a font-family using Open Sans to the body
openSansObserver.load().then(() => {
document.body.classList.add('fontLoaded');
}, () => {
document.body.classList.remove('fontLoaded');
});
// Create redux store with history
const history = createHistory();
const session = createSession({
ns: 'test001',
selectState (state) {
return {
user: state.toJS().user
};
}
});
const store = configureStore(history, session);
const MOUNT_NODE = document.getElementById('app');
const render = (messages) => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE
);
};
if (module.hot) {
// Hot reloadable React components and translation json files
// modules.hot.accept does not accept dynamic dependencies,
// have to be constants at compile-time
module.hot.accept(['./i18n', 'containers/App'], () => {
ReactDOM.unmountComponentAtNode(MOUNT_NODE);
render(translationMessages);
});
}
// Chunked polyfill for browsers without Intl support
if (!window.Intl) {
(new Promise((resolve) => {
resolve(import('intl'));
}))
.then(() => Promise.all([
import('intl/locale-data/jsonp/en.js'),
import('intl/locale-data/jsonp/de.js'),
]))
.then(() => render(translationMessages))
.catch((err) => {
throw err;
});
} else {
render(translationMessages);
}
// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
if (process.env.NODE_ENV === 'production') {
require('offline-plugin/runtime').install(); // eslint-disable-line global-require
}
As you can see from redux-session docs, the only thing this library tries to do to restore saved state is dispatching LOAD_STORED_STATE (which can be customized) action. Restoring your state when the action is dispatched is up to you. The simplest way to restore user state is to handle this action in your userReducer, so that it will look something like:
function userReducer(state = userInitialState, action) {
switch (action.type) {
case LOAD_STORED_STATE:
return fromJS(action.storedState);
case USER_LOGGING_IN:
return state
.set('isLoading', true);
case USER_LOGGED_IN:
return state
.set('data', action.payload)
.set('isLoading', false);
case USER_LOGGED_OUT:
return userInitialState;
default:
return state;
}
}

Categories