I have made a simple react app with the sample code from the following blogpost, which I leave only as a citation. I am otherwise new to the javascript ecosystem and am trying to fit together several unfamiliar tools (in order to learn them).
https://medium.com/#notrab/getting-started-with-create-react-app-redux-react-router-redux-thunk-d6a19259f71f
Relevantly, my store.js looks like this:
import { createStore, applyMiddleware, compose, } from 'redux';
import { connectRouter, routerMiddleware, } from 'connected-react-router';
import thunk from 'redux-thunk';
import { install, } from 'redux-loop';
import createHistory from 'history/createBrowserHistory';
import rootReducer from './modules';
export const history = createHistory();
const initialState = {};
const middleWare = [thunk, routerMiddleware(history),];
const composedEnhancers = compose(
applyMiddleware(...middleWare),
install()
);
const store = createStore(
connectRouter(history)(rootReducer),
initialState,
composedEnhancers
);
export default store;
This seems to work fine, and the Link/Route triggers on my page work fine. Note that install from redux-loop is being called as part of an enhancer (?) and this is fine. I do not have any loop calls in my reducers, I just inserted the install command as an enhancer with the hope that I will be able to add some.
Here is my main reducer code:
import { combineReducers, } from 'redux';
import counter from './counter';
import todo from './todo';
export default combineReducers({
counter,
todo,
});
Again, this works great. However, if I insert loop anywhere in my reducers, it dies. According to the docs, this is because we need to use the combineReducers from redux-loop. Fine. If I replace the import at the top to import { combineReducers, } from 'redux-loop'; (not altering my reducers at all, there are no nonstandard returns yet) then I get some completely nonsensical errors in library code:
ConnectedRouter.js:58 Uncaught TypeError: Cannot read property 'pathname' of undefined
at ConnectedRouter.js:58
at Object.dispatch (redux.js:221)
at dispatch (install.js:66)
at middleware.js:25
at index.js:11
at Object.onLocationChanged (ConnectedRouter.js:154)
at handleLocationChange (ConnectedRouter.js:85)
at new ConnectedRouter (ConnectedRouter.js:94)
at constructClassInstance (react-dom.development.js:11769)
at updateClassComponent (react-dom.development.js:13491)
at beginWork (react-dom.development.js:14090)
at performUnitOfWork (react-dom.development.js:16416)
at workLoop (react-dom.development.js:16454)
at renderRoot (react-dom.development.js:16533)
at performWorkOnRoot (react-dom.development.js:17387)
at performWork (react-dom.development.js:17295)
at performSyncWork (react-dom.development.js:17267)
at requestWork (react-dom.development.js:17155)
at scheduleWork (react-dom.development.js:16949)
at scheduleRootUpdate (react-dom.development.js:17637)
at updateContainerAtExpirationTime (react-dom.development.js:17664)
at updateContainer (react-dom.development.js:17691)
at ReactRoot../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.de...
It goes on for many pages, but the issue seems to be in ConnectedRouter; I assume this is because combineReducers in redux-loop changes the response type of the main reducer into something that's not compatible with the connectRouter(history)(rootReducer) in the createStore call.
Is this the right issue? Is this fixable? Can these two libraries be used together?
There's an open issue that would address this, but until that is done it requires a hack. I called combineReducers with something like this (I am using immutable js. but if you're not it's simple to convert to that)
import { connectRouter } from 'connected-react-router/immutable';
import { Map } from 'immutable';
//....
const routerReducer = connectRouter(history)(() => fromJS({}));
return combineReducers(
{
foo: fooReducer,
blah: blahReducer,
router: (state, action) => {
const routerStateWrapper = Map({router: state});
const result = routerReducer(routerStateWrapper, action);
return result.get('router');
}
}
)
It's already supported in v5+.
Release note: https://github.com/supasate/connected-react-router/releases/tag/v5.0.0
PR: https://github.com/supasate/connected-react-router/pull/150
Related
Redux newbie here.
I have an reducer ProductListReducer that takes asynchronous action (its action creator fetch data from API), whose initial state is InitialProductListStates. I have another synchronous reducer CartReducer, whose initial state is InitialCartListStates. I want to have them work on one single store store. How should I do this?
My attempt: I want to first combine the two reducers into one reducer with combineReducers, and then combine their initial states as InitialState = {ProductListReducer: InitialProductListStates, CartReducer:InitialCartListStates}. Next, I want to use createStore():
store = createStore(reducer, InitialState , applyMiddleware(thunk));
However, I am stuck at the first test-of-concept: After combining ProductListReducer with itself and setting InitialState={ProductListReducer: InitialProductListStates} , I re-run the app and get this error:
Uncaught Error: Minified Redux error #12; visit https://redux.js.org/Errors?code=12 for the full message or use the non-minified dev environment for full errors.
at redux.js:441:13
at Array.forEach (<anonymous>)
at redux.js:434:25
at _l.ProductListReducer (redux.js:499:5)
at main.ts:4:17
at index.tsx:13:27
at index.tsx:13:27
My codes (that doesn't work):
import reducer from './reducers/main';
import { ProductListReducer, InitialProductListStates } from './reducers/reducers'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const InitialStates = {
ProductListReducer: InitialProductListStates
};
export const store = createStore(reducer, InitialStates, applyMiddleware(thunk));
My code (that did work):
import reducer from './reducers/main';
import { ProductListReducer, InitialProductListStates } from './reducers/reducers'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const InitialStates = {
ProductListReducer: InitialProductListStates
};
export const store = createStore(ProductListReducer, InitialProductListStates, applyMiddleware(thunk));
I have two different reducers in my app => nodesReducer and controlsReducer
If I pass them to the store once at a time, they work perfectly fine but whenever I use combineReducers, they stop working => as in everything I pull from the state becomes undefined in my components but everything still looks normal in the toolkit extension.
This is the code.
This works totally fine (single reducer directly put there):
import { configureStore } from "#reduxjs/toolkit";
import nodesReducer from "./Node";
const store = configureStore({
reducer: nodesReducer,
middleware: [],
});
export default store;
And this one does not work when I add one more reducer with combineReducers:
import { configureStore } from "#reduxjs/toolkit";
import { combineReducers } from "redux";
import nodesReducer from "./Node";
import controlsReducer from "./Controls";
const rootReducer = combineReducers({
nodesReducer,
controlsReducer,
});
const store = configureStore({
reducer: rootReducer,
middleware: [],
});
export default store;
Can anyone see what is going wrong?
Bit of an educated guess here, but nothing seems obviously wrong in what you've provided, so given you've only changed the reducer code I'd imagine it's because by combining your reducers, your state will change from:
{
...stuffFromNodesReducer
}
Into
{
nodesReducer: ...stuffFromNodesReducer,
controlsReducer: ...stuffFromControlsReducer
}
You should be able to examine your redux state using redux devtools to check how what it looks like.
After adding rootReducer you should change your useSelectors from
useSelector(state => state.someData)
to
useSelector(state => state.nodesReducer.someData)
useSelector(state => state.controlsReducer.someData)
I have a root reducer which imports action type constants from the another file. When the reducer is called for the first time because of the createstore, the values of these constants are undefined.
I tried converting those constants into functions and it gave me error- "Object can not be function". When I printed its type, it initially gave me undefined but later calls prints the type- function.
My directory structure is such
helpers/
-index.js
-store.js
-constants.js
reducers/
-index.js
-rootReducer.js
Every index.js file is of type-
export * from './rootReducer'
This is my store file (store.js)-
import { createStore, applyMiddleware, compose } from 'redux'
import { rootReducer } from '../reducers'
import ReduxThunk from 'redux-thunk'
const initialState = {}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
export const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(ReduxThunk))
)
This is my constants file (constants.js)-
export const constants = {
BENCHMARK_DATA_RECEIVED: 'BENCHMARK_DATA_RECEIVED',
BENCHMARK_DATA_FAILED: 'BENCHMARK_DATA_FAILED',
}
This is my root reducer (rootReducer.js)-
import { constants } from '../helpers'
export const rootReducer = (state = [], action) => {
console.log(constants)
// Initially print undefined
// After that prints correct value
switch (action.type) {
case 'BENCHMARK_DATA_RECEIVED':
return { ...state, benchmark_data: action.payload }
default:
return state
}
}
I am no sure about what is causing this problem but it only happens when the reducer is used first time ( most probably while creating store ). I have googled a lot but has not come across any such questions. Maybe there is some problem with my setup. Also printing those constants anywhere (like in action creators) works fine.
To sum up the discussion in the comments, you can import directly from constants.js in your reducer and meanwhile investigate the file structure you have.
Im configuring Redux in my React Native project. Unfortunately, I get a problem "Cannot read property 'constructor' of undefined" connected to, I guess, createEpicMiddleware(). Do you have any idea what might cause the problem? Thx!
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { default as OneReducer } from './One';
import { default as TwoReducer } from './Two';
import { navReducer } from './Navigation';
import { createLogger } from 'redux-logger';
import { combineEpics, createEpicMiddleware } from 'redux- observable';
import { epics as OneEpics } from './One/epics';
import { epics as TwoEpics } from './Two/epics';
import { middleware as navMiddleware } from "../navigation";
const enhancer = compose(
applyMiddleware(createLogger())
);
const rootReducer = combineReducers({
one: OneReducer,
two: TwoReducer,
});
const rootEpic = combineEpics(
OneEpics,
TwoEpics
);
const epicMiddleware = createEpicMiddleware();
const store = createStore(rootReducer, applyMiddleware(epicMiddleware, navMiddleware));
epicMiddleware.run(rootEpic);
As far as I can see you the error is here because installed version of rxjs does not match with one which should be used with redux-observable
Updatting rxjs to ">=6.0.0-beta.0 <7" should fix the issue
I am using the latest version of redux-observable and Rxjs i.e
// My version
"redux-observable": "^1.0.0",
"rxjs": "^6.3.2"
The store - middleware, setup looks like this:
// Setting up middlewares
import { pingEpic } from './epics';
import pingReducer from './reducers/pingReducer';
import { combineReducers, createStore, applyMiddleware } from 'redux';
import { combineEpics, createEpicMiddleware } from 'redux-observable';
const rootReducer = combineReducers(pingReducer);
const rootEpic = combineEpics(pingEpic);
const epicMiddleware = createEpicMiddleware();
const store = createStore(rootReducer,
applyMiddleware(epicMiddleware)
);
epicMiddleware.run(rootEpic);
export default store;
And my epic looks like this
// pingEpic.js
import { mapTo } from 'rxjs/operator/mapTo';
import { ofType } from 'redux-observable';
export const pingEpic = action$ => action$.pipe(
ofType('PING'),
mapTo({ type: 'PONG' })
);
So when I executed the program for the first time I got the following error:
I googled it and found a solution here which says to install rxjs-compat#6 (however it doesn't makes any sense) I installed that too! And then I ran into the following error:
.
I don't know what/where am I doing wrong? Any help would be much appreciated!
Thanks
It should be
import { mapTo } from 'rxjs/operators';
instead of
import { mapTo } from 'rxjs/operator/mapTo';
source: https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md#usage