Search code examples
javascriptreactjsreduxconnected-react-routerredux-loop

Are `redux-loop` and `connected-react-router` compatible?


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?


Solution

  • 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');
        }
      }
    )