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
Related
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
I'm reading a code of a react project I found on GitHub and I found a different use of redux thunk, by seeing the official documentation at GitHub they use it this way:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
but in the code I found the guy used:
import { AsyncStorage } from 'react-native';
import { applyMiddleware, createStore } from 'redux';
import { autoRehydrate, persistStore } from 'redux-persist'
import thunk from 'redux-thunk';
import reducers from '../reducers';
const middleWare = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleWare)(createStore);
export default configureStore = (onComplete) => {
const store = autoRehydrate()(createStoreWithMiddleware)(reducers);
persistStore(store, { storage: AsyncStorage }, onComplete);
return store;
};
what is the difference between using it as a list or as an object?
Let's look at the function signature (source):
function applyMiddleware(...middlewares) { /* logic */ }
applyMiddleware uses the rest parameter syntax which allows representing an indefinite number of arguments as an array.
Because of this, both
applyMiddleware(thunk, otherMiddleware);
and
const middlewares = [thunk, otherMiddleware];
applyMiddleware(...middlewares);
are equally valid.
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
I am using Jest and Enzyme to test my React application. Everything works fine but when I import redux store in a utility file, almost all tests get failed. with this error:
FAIL app/containers/Login/LoginContainer.test.js
● Test suite failed to run
TypeError: (0 , _redux.combineReducers) is not a function
at Object.<anonymous> (app/index.jsx:31:2022)
at Object.<anonymous> (app/utils/httpStatusParser.js:3:40)
at Object.<anonymous> (app/utils/requestHandler.js:135:369)
at Object.<anonymous> (app/redux/modules/user.js:148:54)
at Object.<anonymous> (app/containers/Login/LoginContainer.test.js:5:13)
at next (native)
at next (native)
Here is some code:
app/index.jsx:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux'
import routes from '$CONFIG/routes'
import { authUser } from '$REDUX/modules/user'
import { getCookie } from '$UTILS/cookies'
import * as reducers from '$REDUX'
const history = createHistory()
const routeMW = routerMiddleware(history)
export const store = createStore(
combineReducers({
...reducers,
router : routerReducer
}),
compose(applyMiddleware(routeMW, thunk),
window.devToolsExtension ? window.devToolsExtension() : (func) => func)
)
// ...rendering provider to DOM
app/utils/httpStatusParser:
import { push } from 'react-router-redux'
import { store } from '$APP/index.jsx'
import { unauthUser } from '$REDUX/modules/user'
.
.
import { store } from '$APP/index.jsx'
import { unauthUser } from '$REDUX/modules/user'
.
.
.
store.dispatch(unauthUser) // this is when http status is 4XX
requestHandler.js:
import envURLS from '$CONFIG/envURLS'
import httpStatusParser,
{ errorTypeDetailMap } from './httpStatusParser'
import { getCookie } from './cookies'
...
export get() // uses httpStatusParser
export post() // uses httpStatusParser
user.js(Reducer)
import { post } from '$UTILS/requestHandler'
import { errorTypeDetailMap } from '$UTILS/httpStatusParser'
import { setCookie } from '$UTILS/cookies'
...
export unauthUser() // Action Creator
...
Here is one of test file code:
import React from 'react'
import renderer from 'react-test-renderer'
import { mount } from 'enzyme'
import * as userActionCreators from '$REDUX/modules/user'
import { LoginContainer, mapStateToProps } from './LoginContainer'
// Snapshot matching for Login Container
describe('>>> Login Container -- Snapshot Test', () => {
const initialState = userActionCreators.initialState
it('Matches the snapshot with isFetching false', () => {
const tree = renderer.create(<LoginContainer {...initialState} />).toJSON()
expect(tree).toMatchSnapshot()
})
it('Matches the snapshot when log in button is clicked', () => {
const updatedState = {
...initialState,
isFetching : true
}
const tree = renderer.create(<LoginContainer {...updatedState} />).toJSON()
expect(tree).toMatchSnapshot()
})
})
// *************************************************************
LoginContainer:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Login } from '$COMPONENTS'
import { getCookie } from '$UTILS/cookies'
import * as userActionCreators from '$REDUX/modules/user'
The flow is:
Container Calls Async Action Creator from a reducer
Async Action creator makes a get/post call exposed by requestHandler.js
requestHandler imports httpStatusParser to parse the status. If request status in 4XX, the user should be redirected to /login.
httpStatusParser imports store from index.jsx to dispatch unauthUser action.
I couldn't figure out how to resolve this issue. Please let me know if you need anything else.
How are you importing the combineReducers function. Should be
import { combineReducers } from 'redux'
My code is working fine, but I have an annoying problem whenever I make a coding mistake and get a runtime error. For instance, in one of my JSX pages I did Date() instead of new Date() and instead of reporting the actual error, I got...
Uncaught Error: Expected the reducer to be a function.
Any error I make almost always shows up as this. It's being reported from createStore.js, which is in my configureStore.jsx code below.
Is there a way that I can get better error reporting that helps me identify the real problem? Any help or ideas are greatly appreciated!!!
Here's my setup for reference....
main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ReduxRouter } from 'redux-router';
import configureStore from './store/configureStore'
import routes from './routes';
const rootEl = document.getElementById('app-container');
const store = configureStore();
ReactDOM.render(
<div>
<Provider store={store}>
<ReduxRouter routes={routes} />
</Provider>
</div>
, rootEl
);
configureStore.jsx
import { createHashHistory } from 'history';
import { applyMiddleware, createStore, compose } from 'redux';
import { reduxReactRouter } from 'redux-router';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import rootReducer from '../reducers/rootReducer';
import routes from '../routes';
export default function configureStore(initialState = {}) {
const history = createHashHistory();
const middlewares = [
thunk,
promiseMiddleware({
promiseTypeSuffixes: ['PENDING','SUCCESS','ERROR']
})
];
const toolChain = [
applyMiddleware(...middlewares),
reduxReactRouter({
routes,
history
})
];
const store = compose(...toolChain)(createStore)(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers/rootReducer');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
rootReducer.jsx
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import siteReducer from './siteReducer';
const rootReducer = combineReducers({
router: routerStateReducer,
sites: siteReducer
});
export default rootReducer;
siteReducer.jsx
import {GET_SITES} from '../actions/siteActions';
const defaultState = {
isPending: null,
isSuccess: null,
isError: null,
error: null,
data: null
};
export default function siteReducer(state = defaultState, action) {
switch (action.type) {
case `${GET_SITES}_PENDING`:
return {
...defaultState,
isPending: true
};
case `${GET_SITES}_SUCCESS`:
return {
...defaultState,
isSuccess: true,
error: false,
data: action.payload
};
case `${GET_SITES}_ERROR`:
return {
...defaultState,
isError: true,
error: action.payload
};
default:
return state;
}
}
Change the following line:
const nextRootReducer = require('../reducers/rootReducer');
To:
const nextRootReducer = require('../reducers/rootReducer').default;
Use export const variable_name instead of const variable_name whenever you want to export that variable.
For ex: rootReducer.jsx should be re-written as
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import siteReducer from './siteReducer';
export const rootReducer = combineReducers({
router: routerStateReducer,
sites: siteReducer
});
export default rootReducer;
Note the extra export specifier with const rootReducer
My issue was importing Store from the root reducer path rather than the actual bundled store root (with devtools on the window and root reducer, middleware being composed, etc).
import Store from '../../../src/state/Store/reducer';
changed to
import Store from '../../../src/state/Store';