React Boilerplate with Redux Dev Tools Error? - reactjs

I'm using React Boilerplate which uses Redux. I have downloaded the Redux Chrome Dev Tools and I keep getting this error
combineReducers.js:29 The previous state received by the reducer is of unexpected type. Expected argument to be an instance of Immutable.Iterable with the following properties: "route", "language", "global".
I have been debugging this thing through and through to no avail. What I've seen is that combineReducers returns a function and the first line of it is
var inputState = arguments.length <= 0 || arguments[0] === undefined ? _immutable2.default.Map() : arguments[0];
I noticed that a breakpoint here hits twice. The first time, the arguments are exactly what they should be
But the second time my arguments look like this
For some reason, arguments[0] changes from a Map type to a literal object. So two questions:
Why does Redux run combineReducers multiple times on INIT?
Why would my arguments change when initializing?
Here's the code from store.js in React Boilerplate. I only added the import and call for persistStore.
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
import { persistStore, autoRehydrate } from 'redux-persist';
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
/* eslint-enable */
//get the state from localStorage
// const persistedState = loadState();
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers)
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.asyncReducers = {}; // Async reducer registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
});
});
}
persistStore(store);
return store;
}
EDIT
Here is the main reducer file. It brings in other reducer files.
/**
* Combine all reducers in this file and export the combined reducers.
* If we were to do this in store.js, reducers wouldn't be hot reloadable.
*/
import { combineReducers } from 'redux-immutable';
import { fromJS } from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import globalReducer from 'containers/App/reducer';
/*
* routeReducer
*
* The reducer merges route location changes into our immutable state.
* The change is necessitated by moving to react-router-redux#4
*
*/
// Initial routing state
const routeInitialState = fromJS({
locationBeforeTransitions: null,
});
/**
* Merge route into the global application state
*/
function routeReducer(state = routeInitialState, action) {
switch (action.type) {
/* istanbul ignore next */
case LOCATION_CHANGE:
return state.merge({
locationBeforeTransitions: action.payload,
});
default:
return state;
}
}
/**
* Creates the main reducer with the asynchronously loaded ones
*/
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
global: globalReducer,
...asyncReducers,
});
}

Related

Empty Redux store when attempting to dispatch from outside React component

I have what I think is a fairly standard React-Redux setup with some persistence mixed in.
app.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import AppRouter from './routers/AppRouter';
import configureStore from './store/configureStore';
import config from 'cgConfig';
const { store, persistor } = configureStore();
const jsx = (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<AppRouter />
</PersistGate>
</Provider>
);
ReactDOM.render(jsx, document.getElementById(config.constants.element.rootId));
configureStore.js
import {
createStore,
combineReducers
} from 'redux';
import {
persistStore,
persistReducer
} from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import storage from 'redux-persist/lib/storage';
import config from 'cgConfig';
//...import reducers...
export default () => {
const persistConfig = {
key: 'root',
storage,
stateReconciler: autoMergeLevel2
};
let rootReducer = combineReducers({
//...all of my reducers...
});
let store = undefined;
let pr = persistReducer(persistConfig, rootReducer);
if (config.env.includeReduxDevTools) {
store = createStore(
pr,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
} else {
store = createStore(pr);
}
let persistor = persistStore(store);
return {
store,
persistor
};
};
However, the one thing that is a bit unconventional is that I need to update the store outside the context of a react component. As far as I understand (for example), this should not be too difficult to accomplish. Simply call configureStore() to get the store and run a store.dispatch(action). Problem is that I keep getting the initial state of the store back (IE empty), which isn't the same as the one I have already set up through the process of logging in etc. Thus when I run a dispatch, the wrong state is being updated.
Not sure what I am doing wrong, would appreciate any help.
EDIT to answer Uma's question about what the router looks like:
First some more context. The website I am working on will basically have shapes generated in something akin to a graph. These shapes can be manipulated after they are selected from various sources like contextual menus and a toolbar. It is in the context of this change event that I am working on.
When a change is made, the info of the selected item and what needs to be changed will be sent to a generic function which will determine which reducer/action to use to update the Redux store. All my reducers follow the same pattern and look like this:
const reducerInitialState = {};
const setSomeProperty= (state, action) => {
let newState = { ...state };
newState.some_property = action.some_update;
return newState;
};
export default (state = reducerInitialState, action) => {
switch (action.type) {
case 'UPDATE_SOME_PROPERTY':
return setSomeProperty(state, action);
case 'LOG_OUT':
return { ...reducerInitialState };
default:
return state;
}
};
Action for completeness:
export const setSomeProperty = update_data => ({
type: 'UPDATE_SOME_PROPERTY',
some_update: update_data
});
The generic function I have would look something like this:
import configureStore from './store/configureStore';
import { setSomeProperty} from './actions/SomeAction';
import API from './utilities/API';
export default async ({ newValue, selectedShape }) => {
if (!newValue || !selectedShape)
throw "Cannot update the server with missing parameters.";
await API()
.post(
`/api/update-shape`,
{
newValue,
shape: selectedShape
}
)
.then(response => {
updateValue({ selectedShape, newValue });
})
.catch(error => {
// handle error
});
}
const getAction = ({ shape }) => {
switch (shape.type) {
case 0:
return setSomeProperty;
default:
throw `Invalid type of ${shape.type} attempting to update.`;
}
}
const updateValue = ({ selectedShape, newValue }) => {
const action = getAction({ shape: selectedShape })
const { store } = configureStore();
store.dispatch(action(newValue))
}
Note: API is a wrapper for Axios.
Since posting this yesterday I have read that creating a second store like what I am doing with const { store } = configureStore(); is where one of my problems lie in that React/Redux can't have 2 of them. I have also come to realize that the problem most likely have to do with the initial state in my reducers, that somehow using configureStore() does not send the actual state to the reducers and thus all my reducers are showing their initial states when I look at them using console.log(store.getState());. If this is true, at least I know the problem and that is half the battle, but I am unsure how to proceed as I have tried to ReHydrate the state I get from configureStore() but nothing seems to work the way I expect it to.
As far as I can tell you end up in a weird loop where you call an action setSomeProperty then it gets you to the reducer, and in reducer you call that setSomeProperty action again.
In reducer I would expect to see something like this:
export default (state = reducerInitialState, action) => {
switch (action.type) {
case 'UPDATE_SOME_PROPERTY':
return {
...state, // preserve already set state properties if needed
some_update: action.some_update
}
case 'LOG_OUT':
return { ...reducerInitialState };
default:
return state;
}
};

Error: Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware

I wrote a simple counter app. I tried to add a saga middleware to log actions. Very basic app, but i nice structure.
When I add a middleware something went wrong: I had the error:
redux-saga-core.esm.js:1442 Uncaught Error: Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware
at Function.sagaMiddleware.run (redux-saga-core.esm.js:1442)
at createAppStore (Store.tsx:66)
at eval (Store.tsx:70)
at Module../src/store/Store.tsx (bundle.js:1175)
at __webpack_require__ (bundle.js:20)
at eval (App.tsx:6)
at Module../src/components/App.tsx (bundle.js:1127)
at __webpack_require__ (bundle.js:20)
at eval (index.tsx:6)
at Module../src/index.tsx (bundle.js:1151)
I found the problem is the initialiseSagaMiddleware.run(sagaLogger);
but why? It comes just after createStore...
Here my store:
/**
* This is the global store, where the magic appens. Here I have a redurcer who generates
* the new application state and the global state.
* Store = cotainer of reducer and state.
*/
import react from "react";
import { createStore, applyMiddleware, } from "redux";
import {IState,IAction, actionType} from '../types/types'
import createSagaMiddleware from "redux-saga";
import {sagaLogger} from '../middleware/sagaLogger'
const initialiseSagaMiddleware = createSagaMiddleware();
/**
* Application initial state. TODO: may be loaded from an API?
*/
const initialState : IState = {
count: -10
}
/**
* Reducer: function that create a new state. I must be a pure function, no side effects.
* It works only on his parameter and return the new state. Reducer parameter are always the same.
* #param state application global state, ad default it has initialState value.
* #param action action fired by components.
*/
const reducer = (state: IState = initialState, action: IAction): IState => { //Set default to initialState
// console.log(state, action);
const newState : IState = { count: state.count}
switch(action.type){
case actionType.INCREMENT:
newState.count= newState.count+1;
break;
case actionType.DECREMENT:
newState.count=newState.count-1;
break;
default:
newState.count= newState.count;
break;
}
return newState;
};
/**
* AppStore is my store. createStore create my Redux Store.
* TODO: change any to <IState,IAction>
* For use with redux dev tool use: storeEnhancers, compose
*/
const sagaMiddleware = createSagaMiddleware();
const createAppStore = (): any => {
const Store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
initialiseSagaMiddleware.run(sagaLogger);
return Store;
}
export const AppStore = createAppStore();
Here my app.tsx
import React from 'react'
import {Provider} from 'react-redux'
import {AppStore} from "../store/Store";
import Counter from "./counter";
/**
* Provider is part of the Redux magic. It connect react to redux providing to all the children components (all levels) the state.
*/
export default function App():JSX.Element {
return (
<Provider store = {AppStore}>
<div>
<Counter />
</div>
</Provider>
);
}
Here my banbel.rc (I had to define env preset):
{
"presets": [
[
"#babel/preset-env",
{
"targets": {
"browsers": [
"last 2 Chrome versions"
]
}
}
],"#babel/react","#babel/Typescript"]
}
I hope my counter works and action will be logged on the console.
You are creating two saga middlewares and applying one to the store and running your sagas on the other. Remove the const initialiseSagaMiddleware = createSagaMiddleware(); and change createAppStore to:
const sagaMiddleware = createSagaMiddleware();
const createAppStore = (): any => {
const Store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
// use the same saga middleware that you have enhanced your store with
sagaMiddleware.run(sagaLogger);
return Store;
}

How to set up redux-persist with thunk, middleware and immutable JS?

I am trying to get redux-persist working and cannot work out how to do it with the way my app is currently set up.
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import thunk from 'redux-thunk';
import createReducer from './reducers';
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
thunk,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
// Prevent recomputing reducers for `replaceReducer`
shouldHotReload: false,
})
: compose;
/* eslint-enable */
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;
}
Where am I supposed to add redux-persist in here to make it connect to the store and save to localstorage?
I have tried a lot of different ways, all wrong, with a huge range of errors.. So no point posting all of them up.
Thanks in advance for helping :)
I'm not sure if this is of any use as I'm using redux-thunk and redux-localstorage (instead of redux-persist), but this works for me:
import { createStore, compose, applyMiddleware } from 'redux';
import rootReducer from '../reducers/rootReducer';
import thunk from 'redux-thunk';
import persistState from 'redux-localstorage';
const enhancer = compose(
applyMiddleware(thunk),
persistState(/*paths, config*/)
);
export default function configureStore() {
return createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
enhancer
);
}

action.rehydrate is not a function

I have been getting the following error since sometime in my console, i have no idea what it means and why it is originating. Please spread some light on this matter.
it says:
persistReducer.js:50 Uncaught TypeError: action.rehydrate is not a function
at _rehydrate (persistReducer.js:50)
at persistReducer.js:54
redux-persist version on package.json: "^5.6.11"
locked version: "5.9.1"
Store configuration code:
import thunk from 'redux-thunk';
import { persistStore } from 'redux-persist';
import { History, createBrowserHistory } from 'history';
import { createUserManager, loadUser } from "redux-oidc";
import { routerReducer, routerMiddleware } from 'react-router-redux';
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store, StoreEnhancerStoreCreator, ReducersMapObject } from 'redux';
import * as StoreModule from './reducers';
import { ApplicationState, reducers } from './reducers';
import userManager from "./utils/userManager";
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
const history = createBrowserHistory({ basename: baseUrl });
export default function configureStore(history: History, initialState?: ApplicationState) {
// Build middleware. These are functions that can process the actions before they reach the store.
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
// If devTools is installed, connect to it
const devToolsExtension = windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__ as () => GenericStoreEnhancer;
const createStoreWithMiddleware = compose(
applyMiddleware(thunk, routerMiddleware(history)),
devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next
)(createStore);
// Combine all reducers and instantiate the app-wide store instance
const allReducers = buildRootReducer(reducers);
const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;
loadUser(store, userManager);
// Enable Webpack hot module replacement for reducers
if (module.hot) {
module.hot.accept('./reducers', () => {
const nextRootReducer = require<typeof StoreModule>('./reducers');
store.replaceReducer(buildRootReducer(nextRootReducer.reducers));
});
}
const persistor = persistStore(store);
return { store, persistor };
}
function buildRootReducer(allReducers: ReducersMapObject) {
return combineReducers<ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
}
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = (window as any).initialReduxState as ApplicationState;
export const { store, persistor } = configureStore(history, initialState);
if you are working on localhost and using redux-devtools try to check Access File Url checkbox on extension options.
you can manage your chrome extensions by typing chrome://extensions/ in the address bar or by going to the setting and chose extensions from left menu.
I get this error when using redux devtools, remove this and it should go away.
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
Your code is using API of an older redux-persist version.
Refer to Basic Usage for the updated API:
import { persistStore, persistReducer } from 'redux-persist'
const persistConfig = {
key: 'root',
storage,
}
const allReducers = persistReducer(persistConfig, buildRootReducer(reducers));

asyncLocalStorage requires a global localStorage object

I'm trying to persist data using redux-persist. Here is my code:
import { createStore as _createStore, applyMiddleware, compose } from 'redux';
import createMiddleware from './middleware/clientMiddleware';
import { routerMiddleware } from 'react-router-redux';
import {persistStore, autoRehydrate} from 'redux-persist';
export default function createStore(history, client, data) {
// Sync dispatched route actions to the history
const reduxRouterMiddleware = routerMiddleware(history);
const middleware = [createMiddleware(client), reduxRouterMiddleware];
let finalCreateStore;
if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
const { persistState } = require('redux-devtools');
const DevTools = require('../containers/DevTools/DevTools');
finalCreateStore = compose(
applyMiddleware(...middleware),
window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument(),
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(_createStore);
} else {
finalCreateStore = applyMiddleware(...middleware)(_createStore);
}
const reducer = require('./modules/reducer');
// const store = finalCreateStore(reducer, data);
const store = finalCreateStore(reducer, data, autoRehydrate());
if (typeof window !== 'undefined') persistStore(store);
if (__DEVELOPMENT__ && module.hot) {
module.hot.accept('./modules/reducer', () => {
store.replaceReducer(require('./modules/reducer'));
});
}
return store;
}
It works fine with a single flow. But if I refresh page on any page except homePage, everything disturbs on page. I got following warnings as well:
[1] redux-persist asyncLocalStorage requires a global localStorage object. Either use a different storage backend or if this is a universal redux application you probably should conditionally persist like so: https://gist.github.com/rt2zz/ac9eb396793f95ff3c3b
[1] Warning: React can't find the root component node for data-reactid value `.15mzo5h179c.3.0.0`. If you're seeing this message, it probably means that you've loaded two copies of React on the page. At this time, only a single copy of React can be loaded at a time.
P.s. I'm using react-redux-universal-hot-example boilerplate
You want to conditionally create your store using the localStorage store enhancer only when you're running on the client.
...
const reducer = require('./modules/reducer');
let store;
if (__CLIENT__) {
store = finalCreateStore(reducer, data, autoRehydrate());
persistStore(store);
} else {
store = finalCreateStore(reducer, data);
}
...

Resources