React-hot-loader: You cannot change <Router routes>; it will be ignored

Created on 19 May 2016  路  31Comments  路  Source: gaearon/react-hot-loader

Hi, I cannot seem to setup the hot reloading. It actually works, but I keep getting the error in console You cannot change <Router routes>; it will be ignored

This is my router file

import React from 'react';
import ReactDOM from 'react-dom';

import {Provider} from 'react-redux';
import {Router, browserHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
import { getRootNode } from '../../helpers/routing_helpers';

// Components

// Hot reload
import { AppContainer as HotLoaderAppContainer } from 'react-hot-loader';
import AppRoutes from './routes';

export default function (inject: Function, {Store}: IContext) {
  const history = syncHistoryWithStore(browserHistory, Store);

  const renderApp = (CurrentAppRoutes: any) => {
    ReactDOM.render(
      <HotLoaderAppContainer>
        <Provider store={Store}>
          <Router history={history}>
            { CurrentAppRoutes }
          </Router>
        </Provider>
      </HotLoaderAppContainer>,
      getRootNode('react-root')
    );
  };

  renderApp(AppRoutes);

  if (module.hot) {
    module.hot.accept('./routes.jsx', () => {
      const NextAppRoutes = require('./routes').default;
      renderApp(NextAppRoutes);
    });
  }
};

And a routes file

import React from 'react';

import {Route, IndexRoute } from 'react-router';

// Components
import Layout from './components/layout';
import HomePage from './components/home_view';
import ProfilePage from '../user/components/profile_view';

const AppRoutes = (
  <Route path="/" component={Layout}>
    <IndexRoute component={HomePage} />
    <Route path="profile" component={ProfilePage} />
  </Route>
);

export default AppRoutes;

Would you be able to help please?

Most helpful comment

I think this issue should this be kept open util it's officially fixed?

All 31 comments

I have read that thread already quite thoroughly, but I have exactly the SAME proposed solution as per one of the last posts (see code avove) and I still receive the error.

All those are temporary solutions, but have you tried adding a key to the Router component, like e.g. here https://github.com/reactjs/react-router/issues/2704#issuecomment-211352123 ? I haven't tried that myself, but seems to be a good workaround, since the problem is rerendering. I have also seen a variant of this solution with key={ Math.rand() } , which is surely wild, but less fuss.

Yes I tried that and it most certainly helps ;) But, if you add a different key with module reload, you will lose a state and the whole react-hot-reload is kind of useless then ;/

Yes, unfortunately that's how it'd work - different key is a sign for React to remove the React Element and add a new one instead of reconciling it. Therefore the whole state gets thrown away. So you either choose to get the error or to update all the state.

Personally, I don't mind the cannot change <Router routes> error - it shows up, but doesn't interfere with the application. If you have the same feeiling about it, I'd just ignore it and wait for the official fix.

Ok, thanks for the feedback! I'll wait for the official fix then and not mind the error message;)

I think this issue should this be kept open util it's officially fixed?

Any luck on this ?

My Routes file has the following:

<Router history={hashHistory}>
        <Route path="/" component={Main} />
        <Route path="/jobdescription/:id" component={JobDescriptionContainer} />
        <Route path="/apply/:jobdescription" component={ApplicationForm} />
        <Route path="/thankyou" component={ThankYou} />
</Router>

My "solution" is to override the console.error, and filter the warning out. It's only included when HMR is enabled.

/**
 * Warning from React Router, caused by react-hot-loader.
 * The warning can be safely ignored, so filter it from the console.
 * Otherwise you'll see it every time something changes.
 * See https://github.com/gaearon/react-hot-loader/issues/298
 */
if (module.hot) {
  const isString = require('./utils/checks').isString;

  const orgError = console.error; // eslint-disable-line no-console
  console.error = (...args) => { // eslint-disable-line no-console
    if (args && args.length === 1 && isString(args[0]) && args[0].indexOf('You cannot change <Router routes>;') > -1) {
      // React route changed
    } else {
      // Log the error as normally
      orgError.apply(console, args);
    }
  };
}

Was this officially fixed yet?

So it seems like the general consensus is that while the error shouldn't really be there - it's not affecting the hot reloading in any way - right?

The codebase I'm refactoring right now is finally at the point where I'm getting all the correct hot-reloader messages in the console but the changes aren't appearing in my browser until I do a refresh/reload manually. Haven't run into this issue before..

@thebuilder curious, where are you putting this in your code?

@akhayoon where ever you want - i have it in my app.js file.

Well, @thebuilder, thanks for handing me the plastic bag to put over this this annoyances head. That's one way to go about it lol. I'd keep this issue open though until an official fix comes along.

@tsaiDavid Any fix for your problem? I have the same issue: console shows this

[HMR] Updated modules:

process-update.js:102 [HMR]  - ./src/components/App.js

process-update.js:107 [HMR] App is up to date.

but my App component is not updated

It seems like my issue was related to another problem which has a fix now and is/was discussed here: https://github.com/gaearon/react-hot-loader/issues/410

Also for filtering the warning in the console, I prefer not altering the console object and just use a regex in the console filter section of Chrome. It just filters out the warning. The regex looks like this:
^((?!You cannot change \<Router routes\>).)*$

I'm getting the error Warning: [react-router] You cannot change ; it will be ignored

index.js

import React from 'react';
import { render } from 'react-dom';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { AppContainer } from 'react-hot-loader';
import Root from './root';
import configureStore from './store'

const store = configureStore({});
const history = syncHistoryWithStore(browserHistory, store);

render(
  <AppContainer>
    <Root store={ store } history={ history } />
  </AppContainer>,
  document.getElementById('app')
);

if (module.hot) {
  module.hot.accept('./root', () => {
    const NextRoot = require('./root').default;
    render(
      <AppContainer>
        <NextRoot store={ store } history={ history } />
      </AppContainer>,
      document.getElementById('app')
    );
  });
}

root.js

import React, { Component } from 'react';
import ReactDOM from "react-dom";
import { Router, browserHistory } from 'react-router';
import { routerMiddleware, push } from 'react-router-redux';
import routes from './routes';
import { Provider } from 'react-redux';
import configureStore from './store'

export default class Root extends Component {
  render() {
    return (
        <Provider store={ this.props.store }>
            <div>
                <Router history={ this.props.history } routes={ routes } />
            </div>
        </Provider>
    );
  }
}

routes.js

import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './containers/app';

const routes = (
    <Route path='/' component={ App }>
    </Route>
);

export default routes;

webpack.dev.config

var path = require("path");
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    historyApiFallback: true,
    entry: [
        'webpack-dev-server/client?http://localhost:3000',
        'webpack/hot/only-dev-server',
        'react-hot-loader/patch',
        './src/js/index.js'
    ],
    output: {
        path: __dirname + '/static',
        filename: "index_bundle.js",
        publicPath: '/'
    },
    module: {
        loaders: [
            { test: /\.js$/, exclude: /node_modules/, loaders: ["babel-loader"] },
            { test: /\.scss$/, loader: 'style!css!sass' },
            { test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, loader: 'file-loader' }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            inject: true,
            template: __dirname + '/src/' + 'index.html',
            filename: 'index.html'
        }),
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('development')
            }
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin()
    ]
};

app.js

import React, { Component } from 'react';

class App extends Component {
    render() {

        return (
            <div className="app">
                <h1>Reaclux Boilerplate 2</h1>
                <p>A React Redux Webpack Gulp Sass Mocha Enzyme Zombie Chai Boilerplate by <span>Punkbit</span></p>
            </div>
        );

    }

}

export default App;

@heldrida: See #249, the warning comes from React Router. Feel free to ignore it or patch console.error to hide that specific warning. From the comments in https://github.com/ReactTraining/react-router/issues/2182, they probably won't fix this in React Router 3.

It's ok to ignore ? I just don't know why other people stopped having the error.

I made some changes, and get a different error instead "[HMR] The following modules couldn't be hot updated: (They would need a full reload!)"

root.js

import React, { Component } from 'react';
import ReactDOM from "react-dom";
import { Router, browserHistory } from 'react-router';
import { routerMiddleware, push } from 'react-router-redux';
import { Provider } from 'react-redux';
import configureStore from './store'

// include the stylesheet entry point
require('../sass/app.scss');

const Root = (props) => {
    return (
        <Provider store={ props.store }>
            <div>
                <Router history={ props.history } routes={ props.routes } />
            </div>
        </Provider>
    );
}

export default Root;

index.js

import React from 'react';
import { render } from 'react-dom';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { AppContainer } from 'react-hot-loader';
import Root from './root';
import configureStore from './store'
import routes from './routes';

const store = configureStore({});
const history = syncHistoryWithStore(browserHistory, store);

render(
  <AppContainer>
    <Root store={ store } history={ history } routes={ routes } />
  </AppContainer>,
  document.getElementById('app')
);

if (module.hot) {
  module.hot.accept('./root', () => {
    const NextRoot = require('./root').default;
    render(
      <AppContainer>
        <NextRoot store={ store } history={ history } routes={ routes } />
      </AppContainer>,
      document.getElementById('app')
    );
  });
}

@heldrida: you'll need to accept your route config, since that imports all of your components.

@calesce to accept ? like this module.hot.accept('./routes')

Yep, here's a minimal example. We really need to add this to our docs.

@calesce thanks for the suggestion, fixed the warning message but getting the initial one again "Warning: [react-router] You cannot change ; it will be ignored"

index.js

import React from 'react';
import { render } from 'react-dom';
import { router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { AppContainer } from 'react-hot-loader';
import Root from './root';
import configureStore from './store'
import routes from './routes';

const store = configureStore({});
const history = syncHistoryWithStore(browserHistory, store);

render(
  <AppContainer>
    <Root store={ store } history={ history } routes={ routes } />
  </AppContainer>,
  document.getElementById('app')
);

if (module.hot) {
  module.hot.accept('./routes', () => {
    const newRoutes = require('./routes').default;
    const NextRoot = require('./root').default;
    render(
      <AppContainer>
         <NextRoot store={ store } history={ history } routes={ newRoutes } />
      </AppContainer>,
      document.getElementById('app')
    );
  });
}

routes.js

import React from 'react';
import { Route } from 'react-router';
import App from './containers/app';

const routes = () => {
  return (
    <Route path='/' component={ App }>
    </Route>
  );
};

export default routes;

root.js

import React, { Component } from 'react';
import ReactDOM from "react-dom";
import { Router, browserHistory } from 'react-router';
import { routerMiddleware, push } from 'react-router-redux';
import { Provider } from 'react-redux';
import configureStore from './store'

// include the stylesheet entry point
require('../sass/app.scss');

const Root = (props) => {
    return (
        <Provider store={ props.store }>
            <div>
                <Router history={ props.history }>
                { props.routes() }
                </Router>
            </div>
        </Provider>
    );
}

export default Root;

I'm using the temporary fix suggested by Dan Abramov, but not sure if my setup is correct, so have that in mind if you're reading this in the future.

Router.prototype.componentWillReceiveProps = function(nextProps) {
  let components = [];
  function grabComponents(element) {
    // This only works for JSX routes, adjust accordingly for plain JS config
    if (element.props && element.props.component) {
      components.push(element.props.component);
    }
    if (element.props && element.props.children) {
      React.Children.forEach(element.props.children, grabComponents);
    }
  }
  grabComponents(nextProps.routes || nextProps.children);
  components.forEach(React.createElement); // force patching
};

@heldrida as I said before, there's no way to get rid of that warning without patching console.error in your own code. Are your components now reloading?

You don't need that componentWillReceiveProps hack any more, that was an issue in earlier RHL3 versions.

Yes it is reloading, without the compenntWillReceiveProps; Ok, I'll leave the warning, I thought everyone else found a non-hacky solution to remove it, thank you!

Nope, it's hacks all around 馃槃

If others, like me, encounter this when trying to add hot reloading to your react-router based React project from create-react-app, the much simpler solution is to make this change (to index.js):

diff --git a/songsearch/client/src/index.js b/songsearch/client/src/index.js
index 954ab34..4c047b1 100644
--- a/songsearch/client/src/index.js
+++ b/songsearch/client/src/index.js
@@ -9,14 +9,26 @@ import 'bootstrap/dist/css/bootstrap.css';
 import './index.css';


+class Root extends React.Component {
+  render() {
+    return (
+      <Router history={browserHistory}>
+        <Route path='/' component={App}>
+          <IndexRoute component={Home} />
+          <Route path="/q/:q" component={Home}/>
+          <Route path="/song/:artist/:name/:id" component={Song}/>
+          <Route path='/about' component={About} />
+        </Route>
+      </Router>
+    )
+  }
+}
+
 ReactDOM.render(
-  <Router history={browserHistory}>
-    <Route path='/' component={App}>
-      <IndexRoute component={Home} />
-      <Route path="/q/:q" component={Home}/>
-      <Route path="/song/:artist/:name/:id" component={Song}/>
-      <Route path='/about' component={About} />
-    </Route>
-  </Router>,
+  <Root/>,
   document.getElementById('root')
-);
+)
+
+if (module.hot) {
+  module.hot.accept()
+}

The trick is the simplicity of the module.hot.accept() and that you need to put make the first block a component instead of a block of <Router>.

@peterbe yeah it was discussed a bit in #413. A blanket module.hot.accept() actually works well in most cases, but with stateful objects like Redux stores you might hit some rough edges. It's certainly the nicest approach with the least boilerplate.

If others, like me, encounter this when trying to add hot reloading to your react-router based React project from create-react-app, the much simpler solution is to make this change (to index.js):

diff --git a/songsearch/client/src/index.js b/songsearch/client/src/index.js
index 954ab34..4c047b1 100644
--- a/songsearch/client/src/index.js
+++ b/songsearch/client/src/index.js
@@ -9,14 +9,26 @@ import 'bootstrap/dist/css/bootstrap.css';
 import './index.css';


+class Root extends React.Component {
+  render() {
+    return (
+      <Router history={browserHistory}>
+        <Route path='/' component={App}>
+          <IndexRoute component={Home} />
+          <Route path="/q/:q" component={Home}/>
+          <Route path="/song/:artist/:name/:id" component={Song}/>
+          <Route path='/about' component={About} />
+        </Route>
+      </Router>
+    )
+  }
+}
+
 ReactDOM.render(
-  <Router history={browserHistory}>
-    <Route path='/' component={App}>
-      <IndexRoute component={Home} />
-      <Route path="/q/:q" component={Home}/>
-      <Route path="/song/:artist/:name/:id" component={Song}/>
-      <Route path='/about' component={About} />
-    </Route>
-  </Router>,
+  <Root/>,
   document.getElementById('root')
-);
+)
+
+if (module.hot) {
+  module.hot.accept()
+}

The trick is the simplicity of the module.hot.accept() and that you need to put make the first block a component instead of a block of <Router>.

This causes whole page re-render not the changed component

If you are having this issue this is most likely due to createBrowserHistory being in the same file you accept in the HMR. Since the entire file specified will be reloaded, there will be a new history created on each HMR refresh and you will get the error. The simple solution is to create history in a separate file and import it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Anahkiasen picture Anahkiasen  路  5Comments

mqklin picture mqklin  路  3Comments

reintroducing picture reintroducing  路  4Comments

lemonmade picture lemonmade  路  3Comments

JamesIves picture JamesIves  路  4Comments