supasate / connected-react-router

A Redux binding for React Router v4
MIT License
4.73k stars 594 forks source link

Uncaught Could not find router reducer in state tree, it must be mounted under "router" #312

Open LeoHYS opened 5 years ago

LeoHYS commented 5 years ago

When i create a store and combine a reducer, but connectRouter returns 0. But in the official example when i run it, it works and connectRouter returns a object. My code is the same like the official example.

Screen Shot 2019-05-24 at 4 59 54 PM Screen Shot 2019-05-24 at 5 01 08 PM Screen Shot 2019-05-24 at 5 01 52 PM Screen Shot 2019-05-24 at 5 02 54 PM

Could somebody can help me? Pls...

piotrbienias commented 5 years ago

How does your App container look like?

Danziger commented 5 years ago

Make sure you have not defined an initial state for router when calling createStore. That's handled automatically and might cause this issue if location or action is falsy as the check to verify there's a router is:

const isRouter = (value) => value != null &&
    typeof value === 'object' &&
    getIn(value, ['location']) &&
    getIn(value, ['action'])

Where value is state.router.

parkerault commented 5 years ago

I had this issue as well, and it was because I was defining an initial router state. The problem is, if you are using TypeScript, the compiler will report an error if your reducer tree and your default state tree don't share the same structure. It's really confusing because unless you already know that this is a problem, you will follow the TS feedback and end up with a runtime error message that is exactly backwards (the issue isn't that the reducer is missing, it's that you are giving it a default state when it doesn't expect one). Most people will probably spend a half hour trying to figure out why the reducer is undefined when it is most definitely being created, then google the error message and end up here. To make it extra awkward, the only way to make the compiler happy is to create a router key in the state tree with the value (undefined as any) as RouterState. This is one of those cases where most of the trouble is not caused by the error, but by a misleading error message. @supasate is this something that would be relatively easy to change? It would improve the ergonomics a lot and probably save a lot of superfluous github issues.

alexconcatel commented 5 years ago

Hi there.

I'm having this issue too, and it has been for days to try to solve it. I ended in leaving only the router in the state and try to compile my application. As @parkerault said, it has been very confusing for me because I ended on nosense errors.

My approach is to use 'redux-immutable''s combineReducers with 'connected-react-router''s connectRouter in TypeScript, and using immutable.js to make my state tree entirely immutable. I ended to let this small store initialization, following @parkerault 's advice for the declaration, but I'm still getting the 'Could not find router reducer' error.

My store initialization looks like that:

import { applyMiddleware, createStore } from 'redux';
import { createBrowserHistory } from 'history';
import { routerMiddleware, connectRouter, RouterState } from 'connected-react-router/immutable';
import { composeEnhancers } from './utils';
import { combineReducers } from 'redux-immutable';

export const history = createBrowserHistory();

export default function configureStore() {
  const middlewares = [routerMiddleware(history)];
  const enhancer = composeEnhancers(applyMiddleware(...middlewares));
  const store = createStore(
    combineReducers({
      router : connectRouter(history)
    }),
    enhancer
  );
 return store;
}

If I debug this store attaching it to the window, I can see that store is an Immutable map with size == 1 and 2 entries like you can see in this image:

Captura de pantalla 2019-11-26 a las 14 05 15

Besides this, my approach is to combine more reducers than the router one, and make them immutable aswell using redux-immutable combineReducers. I'm almost there, I could type the other states as Immutable records and easily navigate through them with getters. Without the router, I end in this error https://github.com/reduxjs/redux/issues/2808 . Seems to be merged but not to be updated in the package, since I can still see DeepPartial on Store declaration file.

It has been a nightmare for me since days, I need some help or clarifications if I am doing anything wrong. It's a matter of life & death, because is for a test for a new job position, and with this immutable feature surely I can get hired :)

Thx to all in advance.

Kataclan commented 5 years ago

In deeper analysis of the problem, I found that when you don't import ConnectedRouter specifically from connected-react-router/immutable it is created with a plainStructure object, which uses a getIn method for plain JS objects. This method tryes to access the props of the object, in this case our immutable state, with curly braces instead of Iterable.getIn() method.

For me, this TS code works.

AppRouter.tsx:

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router/immutable'; // WHEN I ADDED /IMMUTABLE IT SOLVED MY PROBLEM
import { Switch, Route } from 'react-router';

import configureStore from '../store';
import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();
export const store = configureStore(history);

class AppRouter extends Component {
  render() {
    return (
      <Provider store={store}>
        <ConnectedRouter history={history}>
          <Switch>
            <Route exact path={'/home'} render={() => <div/>} />
          </Switch>
        </ConnectedRouter>
      </Provider>
    );
  }
}
export default AppRouter;

configureStore.ts

import { applyMiddleware, createStore } from 'redux';
import { History } from 'history';
import { routerMiddleware } from 'connected-react-router/immutable';

import { composeEnhancers } from './utils';
import rootReducer from './root-reducer';

export default function configureStore(history: History) {
  //Init middlewares
  const middlewares = [routerMiddleware(history)];

  //Init enhancer
  const enhancer = composeEnhancers(applyMiddleware(...middlewares));

  //Store creation
  const store = createStore(
    rootReducer(history),
    enhancer
  );

  return store;
}

rootReducer.ts

import { History } from 'history';
import { combineReducers } from 'redux-immutable';
import { connectRouter } from 'connected-react-router';

const rootReducer = (history: History<any>) =>
  combineReducers({
    router : connectRouter(history)
  })

export default rootReducer;
packetstracer commented 4 years ago

I'm writing a complex application, and the reducers have a complex hierarchy also (using combineReducers), so the router reducer cannot be placed as a root reducer but in a nested reducer (for example: arch.global.router).

{
  ...,
  arch: {
    ...,
    global: {
      router: connectRouter(history),
      ...
    }
  }
}

Is there a way to config the library so that the router reducer can be located in a nested reducer inside the resulting global reducer?

Thanks in advance.

Abjuk commented 4 years ago

I found a solution. The problem related to version incompatibility. I had this error when using connected-react-router 6.8.0 and react-router 5.2.0. I changed it to connected-react-router 6.6.1 and react-router 5.1.2 and that is all.

xuanlongvts commented 4 years ago

No, just lib history update v5.0.0, i decrease verion 4.10.1, it's work ok

https://github.com/ReactTraining/history/issues/804

brianmriley commented 4 years ago

I can confirm that @xuanlongvts fix by downgrading history to "history": "4.10.1", worked for me as well.

Here's the rest of my setup with package dependencies as well. I did delete a couple non-essential styling pieces and the like for brevity.

App.js

import React from "react";
import { Provider } from "react-redux"
import { MuiThemeProvider } from "@material-ui/core"
import { store } from "./state"
import theme from "./theme"
import { withStyles } from "@material-ui/core";
import {AppRouter} from "./router/AppRouter";

const styles = theme => ({

})

const App = (props) => {
    const { classes } = props
    return (
        <MuiThemeProvider theme={theme}>
            <Provider store={store}>
                <AppRouter />
            </Provider>
        </MuiThemeProvider>
    )
}

export default withStyles(styles)(App)

AppRouter.js

import {Redirect, Route, Switch} from "react-router";
import {LoginContainer} from "../login";
import {Users} from "../users";
import React from "react";
import { routes } from "./app.routes";
import { history } from "./app.history";
import {ConnectedRouter} from "connected-react-router";

export const AppRouter = () => (
    <ConnectedRouter history={history}>
        <Switch>
            <Route exact path={routes.login} component={LoginContainer} />
            <Route exact path={routes.users} component={Users} />
            <Route exact path={routes.base}>
                <Redirect to={routes.login} />
            </Route>
        </Switch>
    </ConnectedRouter>
)

history.js

import { createBrowserHistory } from "history"

/**
 * Creates a history object that uses the HTML5 history API including
 * pushState, replaceState, and the popstate event.
 */
export const history = createBrowserHistory();

reducer.js

import { combineReducers } from "redux"
import { connectRouter } from "connected-react-router"
import { reducer as auth } from "./auth/authetication.reducer"

const createRootReducer = (history) => combineReducers({
    router: connectRouter(history),
    auth
});
export default createRootReducer;

store.js

import { createStore, applyMiddleware, compose } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";
import { logger } from "redux-logger";
import { routerMiddleware } from "connected-react-router"
import createRootReducer from "./reducer";
import rootSaga from "./saga";
import { history } from "../router/app.history";

const sagaMiddleware = createSagaMiddleware();
const middlewares = [
    routerMiddleware(history),
    sagaMiddleware,
]

const enhancer =
    process.env.NODE_ENV === "development"
        ? composeWithDevTools(applyMiddleware(...middlewares, logger))
        : compose(applyMiddleware(...middlewares));

export const store = createStore(
    createRootReducer(history),
    enhancer,
);
sagaMiddleware.run(rootSaga);

package.json

The key here is pinning "history": "4.10.1", to that exact version. Previously I was using the latest ~5.x

{
  "name": "admin-ui-react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@loadable/component": "^5.10.3",
    "@material-ui/codemod": "^4.5.0",
    "@material-ui/core": "^4.5.1",
    "@material-ui/icons": "^4.5.1",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "connected-react-router": "^6.8.0",
    "date-fns": "^2.6.0",
    "formik": "^2.0.6",
    "history": "4.10.1",
    "i18next": "^18.0.1",
    "i18next-xhr-backend": "^3.2.0",
    "lodash": "^4.17.15",
    "query-string": "^6.8.3",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-redux": "^7.1.1",
    "react-router": "^5.2.0",
    "react-router-redux": "^4.0.8",
    "react-scripts": "3.4.1",
    "redux": "^4.0.4",
    "redux-logger": "^3.0.6",
    "redux-saga": "^1.1.1"
  },
  "devDependencies": {
    "enzyme": "^3.10.0",
    "enzyme-adapter-react-16": "^1.15.1",
    "enzyme-to-json": "^3.4.3",
    "eslint-config-prettier": "^6.7.0",
    "eslint-config-standard": "^14.1.0",
    "eslint-config-standard-react": "^9.2.0",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-node": "^10.0.0",
    "eslint-plugin-prettier": "^3.1.1",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-react": "^7.16.0",
    "eslint-plugin-standard": "^4.0.1",
    "fetch-mock": "^8.0.1",
    "gh-pages": "^2.1.1",
    "node-fetch": "^2.6.0",
    "prettier": "^1.19.1",
    "redux-devtools-extension": "^2.13.8"
  },
  "scripts": {
    "start": "react-scripts start --no-cache",
    "build": "react-scripts build",
    "test": "react-scripts test --no-cache",
    "eject": "react-scripts eject",
    "lint": "eslint ./src",
    "lint:fix": "eslint ./src --fix"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
pastinepolenta commented 4 years ago

Pinning history worked for me as well. Perhaps a fix for history > 5.x.x is required ? Or at least add some documentation about this

MartinHaeusler commented 4 years ago

I apparently had the same issue as @stilllife00 and @brianmriley and I can confirm that pinning the version of history to 4.10.1 did indeed resolve the issue. I'm glad that I stumbled across this thread.

rahulkadam commented 4 years ago

Thanks for sharing this thread, very usefull! exact version : 4.10.1 worked for me

wentjun commented 4 years ago

Any plans to fix this such that connected-react-router will not break if we install history v5?

mrcrowl commented 4 years ago

Version 5.0.0 of history is calling handleLocationChange() with a single object argument { location: ..., action: ... }.

Whereas handleLocationChange expects discrete arguments, e.g. handleLocationChange(location, action, isFirstRendering = false).

In fairness to @supasate, the peerDependency for history is correctly set at ^4.7.2.

em-heqian commented 4 years ago

我可以通过将历史记录降级为我工作来确认@xuanlongvts是否已修复"history": "4.10.1",

这也是设置包依赖项的其余部分。为了简洁起见,我确实删除了一些非必要的样式片段等。

App.js

import React from "react";
import { Provider } from "react-redux"
import { MuiThemeProvider } from "@material-ui/core"
import { store } from "./state"
import theme from "./theme"
import { withStyles } from "@material-ui/core";
import {AppRouter} from "./router/AppRouter";

const styles = theme => ({

})

const App = (props) => {
    const { classes } = props
    return (
        <MuiThemeProvider theme={theme}>
            <Provider store={store}>
                <AppRouter />
            </Provider>
        </MuiThemeProvider>
    )
}

export default withStyles(styles)(App)

AppRouter.js

import {Redirect, Route, Switch} from "react-router";
import {LoginContainer} from "../login";
import {Users} from "../users";
import React from "react";
import { routes } from "./app.routes";
import { history } from "./app.history";
import {ConnectedRouter} from "connected-react-router";

export const AppRouter = () => (
    <ConnectedRouter history={history}>
        <Switch>
            <Route exact path={routes.login} component={LoginContainer} />
            <Route exact path={routes.users} component={Users} />
            <Route exact path={routes.base}>
                <Redirect to={routes.login} />
            </Route>
        </Switch>
    </ConnectedRouter>
)

history.js

import { createBrowserHistory } from "history"

/**
 * Creates a history object that uses the HTML5 history API including
 * pushState, replaceState, and the popstate event.
 */
export const history = createBrowserHistory();

reducer.js

import { combineReducers } from "redux"
import { connectRouter } from "connected-react-router"
import { reducer as auth } from "./auth/authetication.reducer"

const createRootReducer = (history) => combineReducers({
    router: connectRouter(history),
    auth
});
export default createRootReducer;

store.js

import { createStore, applyMiddleware, compose } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";
import { logger } from "redux-logger";
import { routerMiddleware } from "connected-react-router"
import createRootReducer from "./reducer";
import rootSaga from "./saga";
import { history } from "../router/app.history";

const sagaMiddleware = createSagaMiddleware();
const middlewares = [
    routerMiddleware(history),
    sagaMiddleware,
]

const enhancer =
    process.env.NODE_ENV === "development"
        ? composeWithDevTools(applyMiddleware(...middlewares, logger))
        : compose(applyMiddleware(...middlewares));

export const store = createStore(
    createRootReducer(history),
    enhancer,
);
sagaMiddleware.run(rootSaga);

package.json

这里的关键是固定"history": "4.10.1",到那个确切的版本。以前我用的是最新的~5.x

{
  "name": "admin-ui-react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@loadable/component": "^5.10.3",
    "@material-ui/codemod": "^4.5.0",
    "@material-ui/core": "^4.5.1",
    "@material-ui/icons": "^4.5.1",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "connected-react-router": "^6.8.0",
    "date-fns": "^2.6.0",
    "formik": "^2.0.6",
    "history": "4.10.1",
    "i18next": "^18.0.1",
    "i18next-xhr-backend": "^3.2.0",
    "lodash": "^4.17.15",
    "query-string": "^6.8.3",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-redux": "^7.1.1",
    "react-router": "^5.2.0",
    "react-router-redux": "^4.0.8",
    "react-scripts": "3.4.1",
    "redux": "^4.0.4",
    "redux-logger": "^3.0.6",
    "redux-saga": "^1.1.1"
  },
  "devDependencies": {
    "enzyme": "^3.10.0",
    "enzyme-adapter-react-16": "^1.15.1",
    "enzyme-to-json": "^3.4.3",
    "eslint-config-prettier": "^6.7.0",
    "eslint-config-standard": "^14.1.0",
    "eslint-config-standard-react": "^9.2.0",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-node": "^10.0.0",
    "eslint-plugin-prettier": "^3.1.1",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-react": "^7.16.0",
    "eslint-plugin-standard": "^4.0.1",
    "fetch-mock": "^8.0.1",
    "gh-pages": "^2.1.1",
    "node-fetch": "^2.6.0",
    "prettier": "^1.19.1",
    "redux-devtools-extension": "^2.13.8"
  },
  "scripts": {
    "start": "react-scripts start --no-cache",
    "build": "react-scripts build",
    "test": "react-scripts test --no-cache",
    "eject": "react-scripts eject",
    "lint": "eslint ./src",
    "lint:fix": "eslint ./src --fix"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

"classnames": "^2.2.6", "connected-react-router": "6.6.1", "core-js": "^3.6.5", "fetch-jsonp": "^1.1.3", "history": "4.10.1", "isomorphic-fetch": "^2.2.1", "lodash": "^4.17.10", "react": "^16.14.0", "react-dom": "^16.14.0", "react-redux": "^7.2.1", "react-router": "^5.1.2", "react-router-config": "^5.1.1", "react-router-dom": "^5.1.2", "redux": "^4.0.4", "redux-logger": "^3.0.6", "redux-saga": "^0.16.0"

I tried it without success

xanden commented 3 years ago

The main issue is the version of the history package, with react-router-dom v5 you need to use history v4 (the latest version of which is 4.10.1) - history v5 is only compatible with react-router-dom v6.

mdubourg001 commented 3 years ago

I faced the same issue: you indeed need to use history@4.10.1, but if you still get the same error after that, check that your history object (export const history = createBrowserHistory()) has the keys location and action and that the values of both these two keys are not nullish.

Here's the condition before the error is thrown in the code:

const isRouter = (value) => value != null &&
  typeof value === 'object' &&
  getIn(value, ['location']) &&
  getIn(value, ['action'])

const getRouter = state => {
  const router = toJS(getIn(state, ['router']))
  if (!isRouter(router)) { throw 'Could not find router reducer in state tree, it must be mounted under "router"' }
  return router
}
marcusjwhelan commented 3 years ago

I still have the issue, and looking at @brianmriley post the only real difference I see is that people are on react 16 and not 17. Which I have tried switching back to and it is still an issue downgrading to 16

StackOverflow Question

Found issue if running in Docker

Turns out it was because I was running the the app in docker and needed to include a PATH for the .bin

# Very specific versions
FROM node:14.15.1-alpine3.11 as react-build-stage

# change working directory to the app directory
WORKDIR /var/www

# copy all files in current directory to working directory
COPY package.json /var/www/

# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH

# install items needed on alpine linux for npm to install modules correctly
RUN apk --no-cache add g++ gcc libgcc libstdc++ linux-headers make python

# install node-gyp -system specific and needs to be installed before modules
RUN npm install --quiet node-gyp -g

# install package.json modules
RUN npm install --silent

# copy all currently made items into /var/www
COPY . /var/www/

# expose this port
EXPOSE 3000
AndriiZil commented 3 years ago

changed history package to 4.10.1 version and solved the problem.

davesag commented 3 years ago

Any word on when it will be safe to upgrade history to version 5+? This issue has lingered now for over two years without a resolution, which is a long time to be stuck using an out of date version of history.

iismaell commented 2 years ago

It is also important to note that if you use redux-immutable you should import ConnectedRouter from 'connected-react-router/immutable'.