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;
}
}
Related
When i login, everything works fine but the moment i hit refresh or navigate somewhere else, the state gets reset. Any idea why? I want to be able to reference the user from the state and get info like name etc and use it inside components. But it only works right after i login and then it will reset.
also, why do i have to use .get in mapstatetoprops? If not i get a Map object. is it because of IMMUTABLE.JS?
Here's my app.js file
/**
* 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 'connected-react-router/immutable';
import jwt_decode from 'jwt-decode';
import FontFaceObserver from 'fontfaceobserver';
import history from 'utils/history';
import 'sanitize.css/sanitize.css';
// Import root app
import App from 'containers/App';
import './styles/layout/base.scss';
// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';
import { setCurrentUser, logoutUser } from './redux/actions/authActions';
import setAuthToken from './utils/setAuthToken';
// Load the favicon and the .htaccess file
import '!file-loader?name=[name].[ext]!../public/favicons/favicon.ico'; // eslint-disable-line
import 'file-loader?name=.htaccess!./.htaccess'; // eslint-disable-line
import configureStore from './redux/configureStore';
// Import i18n messages
import { translationMessages } from './i18n';
// Check for token to keep user logged in
if (localStorage.jwtToken) {
// Set auth token header auth
const token = JSON.parse(localStorage.jwtToken);
setAuthToken(token);
// Decode token and get user info and exp
const decoded = jwt_decode(token);
console.log(decoded);
// Set user and isAuthenticated
setCurrentUser(decoded);
// Check for expired token
const currentTime = Date.now() / 1000; // to get in milliseconds
if (decoded.exp < currentTime) {
// Logout user
logoutUser();
// Redirect to login
window.location.href = './';
}
}
// 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');
});
// Create redux store with history
const initialState = {};
const store = configureStore(initialState, history);
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')]),
) // eslint-disable-line prettier/prettier
.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
}
index.js inside app folder
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import NotFound from 'containers/Pages/Standalone/NotFoundDedicated';
import Auth from './Auth';
import Application from './Application';
import ThemeWrapper, { AppContext } from './ThemeWrapper';
import { Login } from '../pageListAsync';
import PrivateRoute from './PrivateRoute';
const isLoggedIn = localStorage.getItem('jwtToken') !== null ? true : false;
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
console.log(localStorage);
class App extends React.Component {
render() {
return (
<ThemeWrapper>
<AppContext.Consumer>
{changeMode => (
<Switch>
<Route path="/" exact component={Login} />
<PrivateRoute isLoggedIn={isLoggedIn} exact path="/app" component={Application} />
<Route
path="/app"
render={props => <Application {...props} changeMode={changeMode} />}
/>
<Route component={Auth} />
<Route component={NotFound} />
</Switch>
)}
</AppContext.Consumer>
</ThemeWrapper>
);
}
}
export default App;
authactions.js that will setcurrentuser but it only happens once it seems and not again once i reload.
import axios from 'axios';
import jwt_decode from 'jwt-decode';
import setAuthToken from '../../utils/setAuthToken';
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from '../constants/authConstants';
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post('/api/total/users/login', userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem('jwtToken', JSON.stringify(token));
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
console.log('logged');
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
}),
);
};
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
// User loading
export const setUserLoading = () => {
return {
type: USER_LOADING,
};
};
// Log user out
export const logoutUser = history => dispatch => {
// Remove token from local storage
localStorage.removeItem('jwtToken);
// Remove auth header for future requests
setAuthToken(false);
// Set current user to empty object {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
// history.push('/app');
};
EDIT: When i look at redux devtools i see that the IF block does run every time it is refreshed, but the correct state doesn't seem to be passed on to the other components. The other components get the correct state the first time (isAuthenticated: true) but once i refresh, they go back to false. In redux devtools i see this every time i refresh.
main reducer
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { reducer as form } from 'redux-form/immutable';
import { combineReducers } from 'redux-immutable';
import { connectRouter } from 'connected-react-router/immutable';
import history from 'utils/history';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import uiReducer from './modules/ui';
import initval from './modules/initForm';
import login from './modules/login';
import treeTable from '../containers/Tables/reducers/treeTbReducer';
import crudTable from '../containers/Tables/reducers/crudTbReducer';
import crudTableForm from '../containers/Tables/reducers/crudTbFrmReducer';
import ecommerce from '../containers/SampleApps/Ecommerce/reducers/ecommerceReducer';
import contact from '../containers/SampleApps/Contact/reducers/contactReducer';
import chat from '../containers/SampleApps/Chat/reducers/chatReducer';
import email from '../containers/SampleApps/Email/reducers/emailReducer';
import calendar from '../containers/SampleApps/Calendar/reducers/calendarReducer';
import socmed from '../containers/SampleApps/Timeline/reducers/timelineReducer';
import taskboard from '../containers/SampleApps/TaskBoard/reducers/taskboardReducer';
/**
* Branching reducers to use one reducer for many components
*/
function branchReducer(reducerFunction, reducerName) {
return (state, action) => {
const { branch } = action;
const isInitializationCall = state === undefined;
if (branch !== reducerName && !isInitializationCall) {
return state;
}
return reducerFunction(state, action);
};
}
/**
* Merges the main reducer with the router state and dynamically injected reducers
*/
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
form,
ui: uiReducer,
initval,
login,
socmed,
calendar,
ecommerce,
contact,
chat,
email,
taskboard,
treeTableArrow: branchReducer(treeTable, 'treeTableArrow'),
treeTablePM: branchReducer(treeTable, 'treeTablePM'),
crudTableDemo: branchReducer(crudTable, 'crudTableDemo'),
crudTableForm,
crudTbFrmDemo: branchReducer(crudTableForm, 'crudTbFrmDemo'),
language: languageProviderReducer,
router: connectRouter(history),
...injectedReducers,
});
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history);
return mergeWithRouterState(rootReducer);
}
createStore.js
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import { fromJS } from 'immutable';
import createSagaMiddleware from 'redux-saga';
import thunk from 'redux-thunk';
import createReducer from './reducers';
export default function configureStore(initialState = {}, history) {
let composeEnhancers = compose;
const reduxSagaMonitorOptions = {};
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
/* eslint-disable no-underscore-dangle */
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true });
}
// NOTE: Uncomment the code below to restore support for Redux Saga
// Dev Tools once it supports redux-saga version 1.x.x
if (window.__SAGA_MONITOR_EXTENSION__)
reduxSagaMonitorOptions = {
sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
};
/* eslint-enable */
}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middleware = [thunk];
const middlewares = [...middleware, sagaMiddleware, routerMiddleware(history)];
const enhancers = [applyMiddleware(...middlewares)];
const store = createStore(createReducer(), fromJS(initialState), composeEnhancers(...enhancers));
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}
the main app file
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import NotFound from 'containers/Pages/Standalone/NotFoundDedicated';
import jwtDecode from 'jwt-decode';
import configureStore from '../../redux/configureStore';
import { setCurrentUser, logoutUser } from '../../redux/actions/authActions';
import setAuthToken from '../../utils/setAuthToken';
import Auth from './Auth';
import Application from './Application';
import ThemeWrapper, { AppContext } from './ThemeWrapper';
import { PrivateRoute } from './PrivateRoute';
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
const initialState = {};
const store = configureStore(initialState);
const auth = localStorage.jwtToken ? true : false;
// Check for token to keep user logged in
if (localStorage.jwtToken) {
// Set auth token header auth
const token = JSON.parse(localStorage.jwtToken);
setAuthToken(token);
// Decode token and get user info and exp
const decoded = jwtDecode(token);
// Set user and isAuthenticated
store.dispatch(setCurrentUser(decoded));
// Check for expired token
const currentTime = Date.now() / 1000; // to get in milliseconds
if (decoded.exp < currentTime) {
// Logout user
logoutUser();
// Redirect to login
window.location.href = './';
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: {},
};
}
render() {
return (
<ThemeWrapper>
<AppContext.Consumer>
{changeMode => (
<Switch>
{/* <Route path="/" exact component={Login} /> */}
<PrivateRoute
path="/app"
auth={auth}
component={props => <Application {...props} changeMode={changeMode} />}
/>
<Route component={Auth} />
<Route component={NotFound} />
</Switch>
)}
</AppContext.Consumer>
</ThemeWrapper>
);
}
}
export default App;
infectreducer
import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { ReactReduxContext } from 'react-redux';
import getInjectors from './reducerInjectors';
/**
* Dynamically injects a reducer
*
* #param {string} key A key of the reducer
* #param {function} reducer A reducer that will be injected
*
*/
export default ({ key, reducer }) => WrappedComponent => {
class ReducerInjector extends React.Component {
static WrappedComponent = WrappedComponent;
static contextType = ReactReduxContext;
static displayName = `withReducer(${WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'})`;
constructor(props, context) {
super(props, context);
getInjectors(context.store).injectReducer(key, reducer);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return hoistNonReactStatics(ReducerInjector, WrappedComponent);
};
const useInjectReducer = ({ key, reducer }) => {
const context = React.useContext(ReactReduxContext);
React.useEffect(() => {
getInjectors(context.store).injectReducer(key, reducer);
}, []);
};
export { useInjectReducer };
reducerinjector
import invariant from 'invariant';
import { isEmpty, isFunction, isString } from 'lodash';
import checkStore from './checkStore';
import createReducer from '../redux/reducers';
export function injectReducerFactory(store, isValid) {
return function injectReducer(key, reducer) {
if (!isValid) checkStore(store);
invariant(
isString(key) && !isEmpty(key) && isFunction(reducer),
'(app/utils...) injectReducer: Expected `reducer` to be a reducer function',
);
// Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different
if (
Reflect.has(store.injectedReducers, key)
&& store.injectedReducers[key] === reducer
) return;
store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
store.replaceReducer(createReducer(store.injectedReducers));
};
}
export default function getInjectors(store) {
checkStore(store);
return {
injectReducer: injectReducerFactory(store, true),
};
}
Use persistedState. This is index.js file example
function saveToLocalStorage(state) {
try {
const serializedState = JSON.stringify(state)
localStorage.setItem('state', serializedState)
} catch (err) {
console.log(err)
}
}
function loadFromLocalStorage() {
try {
const serializedState = localStorage.getItem('state');
if (serializedState === null) return undefined;
return JSON.parse(serializedState)
} catch (err) {
console.log(err)
return undefined;
}
}
const persistedState = loadFromLocalStorage();
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
persistedState,
applyMiddleware(logger, sagaMiddleware))
sagaMiddleware.run(watchLoadData);
store.subscribe(() => saveToLocalStorage(store.getState()))
ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById('root')
);
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.
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.
I try to use Redux with next.js starter project and I installed next-redux-wrapper on the project but I'm not sure where is the root file in this project.
I try to follow the tutorial shown on the next-redux-wrapper but had no success. Nothing change.
Please help me with how to add Redux to the project.
Regards.
Next.js uses the App component to initialize pages. You can override it and control the page initialization.
Although this demo is for next.js it should work for nextjs-starter.
install next-redux-wrapper:
npm install --save next-redux-wrapper
Add _app.js file to ./pages directory:
// pages/_app.js
import React from "react";
import {createStore} from "redux";
import {Provider} from "react-redux";
import App, {Container} from "next/app";
import withRedux from "next-redux-wrapper";
const reducer = (state = {foo: ''}, action) => {
switch (action.type) {
case 'FOO':
return {...state, foo: action.payload};
default:
return state
}
};
/**
* #param {object} initialState
* #param {boolean} options.isServer indicates whether it is a server side or client side
* #param {Request} options.req NodeJS Request object (not set when client applies initialState from server)
* #param {Request} options.res NodeJS Request object (not set when client applies initialState from server)
* #param {boolean} options.debug User-defined debug mode param
* #param {string} options.storeKey This key will be used to preserve store in global namespace for safe HMR
*/
const makeStore = (initialState, options) => {
return createStore(reducer, initialState);
};
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
// we can dispatch from here too
ctx.store.dispatch({type: 'FOO', payload: 'foo'});
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return {pageProps};
}
render() {
const {Component, pageProps, store} = this.props;
return (
<Container>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</Container>
);
}
}
export default withRedux(makeStore)(MyApp);
And then, actual page components can be simply connected:
This demo how to connect index.js in pages.
import Link from "next/link";
import React from "react";
import {
Container,
Row,
Col,
Button,
Jumbotron,
ListGroup,
ListGroupItem
} from "reactstrap";
import Page from "../components/page";
import Layout from "../components/layout";
import { connect } from "react-redux";
class Default extends Page {
static getInitialProps({ store, isServer, pathname, query }) {
store.dispatch({ type: "FOO", payload: "foo" }); // component will be able to read from store's state when rendered
return { custom: "custom" }; // you can pass some custom props to component from here
}
render() {
return (
<Layout>content...</Layout>
);
}
}
export default connect()(Default);
Refer to the documentation for more information: next-redux-wrapper
First i created simple next.js app with "npx create-next-app"
Then I created general redux setup in a folder called "store".
This is the folder structure
And in pages i have created an _app.js. Code inside it is like this-
Please let me know if anyone needs any help with setup...
this question needs update:
"next": "12.1.4",
"next-redux-wrapper": "^7.0.5",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.4.1"
Create Store
import { createStore, applyMiddleware } from "redux";
import { HYDRATE, createWrapper } from "next-redux-wrapper";
import reducers from "./reducers/reducers";
import thunkMiddleware from "redux-thunk";
// middleware is an array
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
// this is main reducer to handle the hydration
const reducer = (state, action) => {
// hydration is a process of filling an object with some data
// this is called when server side request happens
if (action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
};
return nextState;
} else {
// whenever we deal with static rendering or client side rendering, this will be the case
// reducers is the combinedReducers
return reducers(state, action);
}
};
const initStore = () => {
return createStore(reducer, bindMiddleware([thunkMiddleware]));
};
export const wrapper = createWrapper(initStore);
_app.js
import { wrapper } from "../redux/store";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default wrapper.withRedux(MyApp);
Using it with getServerSideProps
// sample action
import { getRooms } from "../redux/actions/roomActions";
import { wrapper } from "../redux/store";
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
// destructuring context obj
async ({ req, query }) => {
await store.dispatch(getRooms(req, query.page));
}
);
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.