Skip to content
This repository has been archived by the owner on Oct 26, 2018. It is now read-only.

Getting You cannot change <Router routes>; it will be ignored when hot-loading #179

Closed
Tracked by #2
Sawtaytoes opened this issue Jan 9, 2016 · 62 comments
Closed
Tracked by #2

Comments

@Sawtaytoes
Copy link

When hot-loading I am getting the warning:

You cannot change <Router routes>; it will be ignored

My process is I save a file in view with some update like I added a <span>, and it shows up on the page, but then this error shows. I checked into it, finding the commit here: remix-run/react-router@62f6ef2

I don't know what this does. It looks like it's saying if you're on the current page and the route doesn't change, but everything else stayed the same, then warn.

@ppalladino
Copy link

+1 Same issue.

@jlongster
Copy link
Member

This sounds like a react-router issue. Please file there; re-open this if I'm wrong.

@tomasz-szymanek
Copy link

+1 same here. reproduced by doing this:

const renderAll = () => {
  ReactDOM.render(
    (
      <Provider store={store}>
        <Router history={browserHistory}>
          <Route path="/" component={App}>
            <Route path="about" component={About} />
            <Route path="posts" component={Posts}>
              <Route path="/post/:nr" component={Post} />
            </Route>
            <Route path="*" component={NoMatch} />
          </Route>
        </Router>
      </Provider>
    ), document.getElementById('root')
  );
};

store.subscribe(renderAll);
renderAll();

@amertak
Copy link

amertak commented Mar 16, 2016

Started to get the same error.

@haim-rubin
Copy link

Hi,
Is someone of you found solution for this error?
Thanks!

@amertak
Copy link

amertak commented Apr 5, 2016

I found solution for my problem.
I am using shared component for all other components as a parent (it has services and other things in constructor). The problem was, that also containers used this shared component and change of every component in app went directly to router through container.
My "solution" was not to use this shared component for Containers (components defined in router routes). Hope this helps

@lauterry
Copy link

lauterry commented Apr 7, 2016

hi @amertak

I have the same issue.

I can't see what you're talking about.

Can you explain with some code snippet please ?

Best regards

@amertak
Copy link

amertak commented Apr 8, 2016

Hi @lauterry I'll try.

<Provider store={store}> <Router history={history}> <Route path="/sessions" component={Sessions} /> <Route path="/recordings" component={Recordings} /> <IndexRoute component={VisitorList}/> </Router> </Provider>

My problem was, that all the containers (Sesions, Recordings, VisitorList) had been using as a parent not Component from React, but my component called DashboardComponent

export default class DashboardComponent extends Component { ...

Also all other components in app were using DashboardComponent so if I changed something it went up to the container -> changed the router -> threw error

I have to point out that this might not be the same issue as you have.

@OriolBonjoch
Copy link

OriolBonjoch commented Jun 20, 2016

Hi all,

Same happened here and the solution was declaring the routes object outside of the render method as PlainRoutes, so it doesn't get updated. So taking @tomasz-szymanek code it would be something like:

const routes = {
    path: '/',
    component: App,
    childRoutes: [
        { path: '/about', component: About },
        {
            path: '/posts',
            component: Posts,
            childRoutes: [ { path: '/post/:nr', component: Post } ]
        },
        { path: '*', component: NoMatch}
    ]
};

const renderAll = () => {
  ReactDOM.render(
    (
      <Provider store={store}>
        <Router history={browserHistory} routes={routes} />
      </Provider>
    ), document.getElementById('root')
  );
};

store.subscribe(renderAll);
renderAll();

That way the routes list is not changed every time renderAll is executed and react-router doesn't complain.

@boneyao
Copy link

boneyao commented Jul 1, 2016

thanks @OriolBonjoch it works for me!

@tomasz-szymanek
Copy link

You are god to me @OriolBonjoch

@nakamorichi
Copy link

nakamorichi commented Jul 20, 2016

The PlainRoutes approach suggested by @OriolBonjoch didn't work for me. Is there an actual working example of react-router-redux with hot reloading?

@OriolBonjoch
Copy link

First of all that is a warning that doesn't stop anything, its just annoying.

I think this happens because of react-redux top level Provider component. It triggers the componentWillReceiveProps in Route component even when they haven't really changed (component and path remains the same but is a new object since you created it inside render function).

Besides that I'm curious, @Kitanotori in what case you modified the routes in runtime?

@nakamorichi
Copy link

@OriolBonjoch It stops the hot reloading. The changes I make to the route components are not being hot reloaded.

I will post an example app to Github soon in order to explain the issue better.

@nakamorichi
Copy link

@OriolBonjoch It seems that I somehow got it working in the example I built. I wonder what I did differently in my main project... Have to examine further.

Here is an example of how to set up React, Redux, Saga, and routing together with Webpack, Babel, and hot reloading: https://github.com/Kitanotori/react-webpack-babel-hotreload-example

@dceddia
Copy link

dceddia commented Aug 23, 2016

@Kitanotori I think the reason your approach worked is because you extracted the routes into a module, so they were only declared once, instead of recreating them with every render.

I wasn't using react-router-redux, but I did have some state in my Root component for growl-style notifications, and I was getting this "cannot change Router..." warning whenever Root re-rendered.

I was able to get the warning to go away by extracting my routes. Before:

class Root extends Component {
  render() {
    return (
      <Router history={browserHistory} createElement={this.createElement}>
        <Route component={App}>
          <Route path="/" component={MainPage}/>
          <Route path="/page2" component={Page2}/>
          <Route path="/settings" component={SettingsPage}/>
        </Route>
      </Router>
    )
  }
}

After:

const routes = (
  <Route component={App}>
    <Route path="/" component={MainPage}/>
    <Route path="/page2" component={Page2}/>
    <Route path="/settings" component={SettingsPage}/>
  </Route>
);

class Root extends Component {
  render() {
    return (
      <Router history={browserHistory} createElement={this.createElement}>
        {routes}
      </Router>
    )
  }
}

@deryost
Copy link

deryost commented Sep 22, 2016

In my case the bug was the forceUpdate method called by subscribing to the redux store...

@jeznag
Copy link

jeznag commented Sep 24, 2016

In my case, the issue seems to occur when passing props to the routes. When the props change, the warning is fired.

<Router history={browserHistory}>
        <Route path='/' component={LoginComponent} />
        <Route path='/login' component={LoginComponent} />
        <Route path='/chooseCourse' component={ChooseCourseComponent} {...this.props} />
        <Route path='/doCourse/:courseID' component={DoCourseComponent} {...this.props} />
        <Route path='/doCourse/:courseID/:sectionID/:topicID' component={DoCourseComponent} {...this.props} />
      </Router>

I don't know how to get around this. Any ideas?

@hiddentao
Copy link

I had this problem too and it turned out it was due to the same thing as @amertak had. I had a base component which all my other components inherited, so every change caused HMR to propagate up to route definitions and then subsequently the root component. Bizarre.

@OriolBonjoch
Copy link

@jeznag those properties are for Route component (like history or children)? If its not like that I would add it in ChooseCourseComponent and DoCourseComponent connect props.

@jeznag
Copy link

jeznag commented Oct 1, 2016

@OriolBonjoch yeah good point. I ended up doing that :) Misunderstood redux and thought I could only have one connected component.

@hiddentao
Copy link

in case it helps anyone else, here is my code:

// app.js
...
const store = createStore(),
  routes = createRoutes(store);

ReactDOM.render(
  <Provider store={store}>
    <Router history={hashHistory}>
      {routes}
    </Router>
  </Provider>,
  document.getElementById('react-root')
);

// routes.js
...
export function create(store) {
  ...
  return (
    <Route>
      <IndexRoute component={InitPage} />
      <Route path="/editor" component={EditorPage}/>
      <Route path="*" component={InitPage}/>
    </Route>    
  );
}

Ensure your components inherit directly from React.Component and not some intermediate base class. This was what caused problems for me. If I figure out how to make it work with an intermediate base class I'll let you know.

@ghost
Copy link

ghost commented Oct 2, 2016

I am unable to get this working with getComponent. I'm trying to code split my app by routes using System.import and Webpack 2. Although I am unable to get code splitting working using any method for some reason, hot reload does not work when using both getComponent and Provider:

App.js:

...
const store = configureStore();
const history = syncHistoryWithStore(browserHistory, store);

const mountNode = document.querySelector('#app');

render(
    <AppContainer>
        <Root store={store} history={history} />
    </AppContainer>, mountNode);

if (module.hot)
    module.hot.accept('./Root', () => {
        render(
            <AppContainer>
                <Root store={store} history={history} />
            </AppContainer>, mountNode);
    });

Root:

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

routes.js:

import MainLayout from 'containers/MainLayout/MainLayout';
import index from './views/Index/routes';
...

export default {
    component: MainLayout,
    childRoutes: [
        index,
        ...
    ]
}

index routes.js:

import Container from './Container';
import { loadRoute, errorLoading } from 'helpers/route';

export default {
    path: '/',
    getComponent: (location, cb) => System.import('./Container').then(loadRoute(cb)).catch(errorLoading)
};

Container.jsx:

export default class Container extends React.Component {
    render() {
        return (
            <div>
                Home
            </div>
        );
    }
}

@hiddentao
Copy link

@DragonFire353 Why do you repeat the render in the reload hook?

if (module.hot)
    module.hot.accept('./Root', () => {
        render(
            <AppContainer>
                <Root store={store} history={history} />
            </AppContainer>, mountNode);
    });

@ghost
Copy link

ghost commented Oct 3, 2016

@ghost
Copy link

ghost commented Oct 3, 2016

hmmm it works if I make Root an actual React Component:

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

Not sure why there'd be a difference with this when it was working without Provider. Still get the warning though. And code splitting doesn't work but that's a separate issue...

@markbame
Copy link

markbame commented Oct 6, 2016

thanks!

@heldrida
Copy link

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;

@wonbyte
Copy link

wonbyte commented Dec 26, 2016

@adrianmcli Thanks, that simplifies things a ton. But reading the react-hot-loader docs for version 3 states the following:

AppContainer is a component that handles module reloading, as well as error handling. The root component of your app should be nested in AppContainer as a child. When in production, AppContainer is automatically disabled, and simply returns its children.

React Hot Loader 3 does not hide the hot module replacement API, so the following needs to be added below wherever you call ReactDOM.render in your app:

import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './containers/App'

ReactDOM.render(
  <AppContainer>
    <App/>
  </AppContainer>,
  document.getElementById('root')
);

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./containers/App', () => {
    const NextApp = require('./containers/App').default;
    ReactDOM.render(
      <AppContainer>
        <NextApp/>
      </AppContainer>,
      document.getElementById('root')
    );
  });
}

https://github.com/gaearon/react-hot-loader/tree/next/docs

@adrianmcli
Copy link

adrianmcli commented Dec 26, 2016

@wonbyte I'm not using version 3 of React Hot Loader. I'm using the latest stable version from NPM, which is 1.3.1. It's been stable and battle-tested for a long long time.

Version 3 has been "on the horizon" for almost a year now. I'm not holding my breath until they come out with an actual npm release.

What I would recommend is that you try to get everything working with 1.3.1 (the latest stable version). And then incrementally migrate to 3.0 with the supplied guide.

@satyadeeproat
Copy link

How to pass props to route. I am doing this but still getting the error

const routeConfig = (type) => {
  console.log("at route", type)
  return (      
  <Route>
  <Route path='/' component={Template}>
    <Route path='/pageform(/:id)' component={PageForm} />
  </Route>
  
  <Route path='/:pageSlug' component={Page} slug=type/>
  </Route>
);
}
class App extends React.Component {
  render() {
    const slug = this.props
    console.log("at app", slug)

    return (
      <MuiThemeProvider muiTheme={getMuiTheme()}>
        <Provider store={store}>
          <Router history={browserHistory} routes={routeConfig(slug)} />
        </Provider>
      </MuiThemeProvider>
    );
  };
};

@mocheng
Copy link

mocheng commented Jan 12, 2017

Same issue hits me. @dceddia 's solution saved my day.

@chotalia
Copy link

adding the key attribute in <Router> worked for me.

<Router key={Math.random()} history={browserHistory}>
        <Route path='/' component={LoginComponent} />
        <Route path='/login' component={LoginComponent} />
...
</Router>

ondrejrohon added a commit to ondrejrohon/react-bknd that referenced this issue Feb 3, 2017
@ngocketit
Copy link

Thanks @chotalia! That works for me.

@raminious
Copy link

@chotalia your solution is not good and has side effect, obviously it changes all components states to their default states.

@alandotcom
Copy link

Adding a shouldComponentUpdate to my Routes component works for me.

class Routes extends Component {
  shouldComponentUpdate () {
    return false
  }

  render () {
    return (
      <Router>
      //....
      </Router>
    )
  }
}

Then rendering that in my Root component

@heldrida
Copy link

@LumberJ mind sharing the source code? I tried that approach without success; You sure the warning message stopped showing?

@alandotcom
Copy link

alandotcom commented Feb 21, 2017

@heldrida the entire thing looks something like this: (leaving out details like setting up history and the redux store)

// App.jsx

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

const App = ({children}) => (
  <div className='app-container'>
     {children}
  </div>
)

class Routes extends Component {
  shouldComponentUpdate () {
    return false
  }

  render () {
    return (
      <Router history={this.props.history}>
        <Route path='/' component={App}>
          <IndexRoute component={App} />
        </Route>
      </Router>
    )
  }
}

class Root extends Component {
  render () {
    const {store, history} = this.props
    return (
      <Provider store={store}>
        <Routes history={history} />
      </Provider>
    )
  }
}
// index.jsx
import { AppContainer } from 'react-hot-loader'

ReactDOM.render(
  <AppContainer>
    <App store={store} history={history} />
  </AppContainer>,
  document.getElementById('root')
)

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./App', () => {
    const NextApp = require('./App').default
    ReactDOM.render(
      <AppContainer>
        <NextApp store={store} history={history} />
      </AppContainer>,
      document.getElementById('root')
    )
  })
}

@sandrinodimattia
Copy link

Here's what worked for me:

// Render the application.
render(
  <Provider store={store}>
    {router(history)}
  </Provider>,
  document.getElementById('app')
);

// Support for hot reloading.
if (module.hot) {
  import('react-hot-loader').then(({ AppContainer }) => {
    module.hot.accept('./router', () => {
      import('./router')
      .then((routerModule) => {
        render(
          <AppContainer>
            <Provider store={store}>
              {routerModule.default(history)}
            </Provider>
          </AppContainer>,
          document.getElementById('app')
        );
      });
    });
  });
}

@arshmakker
Copy link

@LumberJ Thanks man, this worked for me too. Wonder what's the logic behind this?

@haim-rubin
Copy link

haim-rubin commented Mar 21, 2017 via email

@PavelPolyakov
Copy link

Here is the solution which worked in my case:

import * as React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {AppContainer} from 'react-hot-loader';

import createHistory from "history/createBrowserHistory";
const history = createHistory();

const root = document.getElementById('root');

const render = (Component, props = {}) => {
    ReactDOM.render(
        <AppContainer>
            <Component {...props}/>
        </AppContainer>,
        root
    );
}

render(App, { history });

// Hot Module Replacement API
if (module.hot) {
    module.hot.accept('./App', () => {
        render(App, { history });
    });
}

How it was before:

import * as React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {AppContainer} from 'react-hot-loader';

const render = (Component, props = {}) => {
    ReactDOM.render(
        <AppContainer>
            <Component/>
        </AppContainer>,
        document.getElementById('root')
    );
}

render(App);

// Hot Module Replacement API
if (module.hot) {
    module.hot.accept('./App', () => {
        render(App);
    });
}

And the error:
image

However, as you see - main thing is to put history declaration out of the App component.
I don't find it very neat, as according to my view these property should be declared in the App component itself.

As this repository is moving into the main react-router one, I think it would be very helpful to add some official the solution into the react-router FAQ.

@sliultron
Copy link

Hi, @PavelPolyakov i found an interesting problem with the working code you provided. if i add react-hot-loader/babel plugin in .babelrc as the documentation shows

  "plugins": [
   "react-hot-loader/babel"
   // Enables React code to work with HMR.
 ]

then, i will still see the warning message. i am using "react-hot-loader@v3.0.0-beta.6". The warning message goes away when removing that plugin. Can anyone help to explain why?

Thanks

@yangfan0095
Copy link

I also encountered this problem in my project , but I find my react-router vision is 2.xxx , then I browsed the docs Migrating from v2/v3 to v4 , and I migrating the version to 4.x , this problem never happen .

https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md

it shows :
In v3, the was not really a component. Instead, all of your application's elements were just used to created a route configuration object.
With v4, you layout your app's components just like a regular React application. Anywhere that you want to render content based on the location (specifically, its pathname), you render a .

The v4 component is actually a component. so wherever you render a component, content will be rendered. When the 's path matches the current location, it will use its rendering prop (component, render, or children) to render. When the 's path does not match, it will render null.

nepeat added a commit to rLoopTeam/react-groundstation that referenced this issue Aug 21, 2017
Squashed changes needed to make this happen
* Separate routes to a separate file and add key to App.
reactjs/react-router-redux#179
* Add react-hot-loader boilerplates to app.
* Add node-sass, react-hot-loader to webpack config.
@shidaping
Copy link

shidaping commented Nov 21, 2017

call ReactDOM.unmountComponentAtNode. it will work!

ReactDOM.render((
  <Provider store={store}>
    <Router routes={routes} history={history} />
  </Provider>
), document.getElementById('app'));

if (module.hot) {
  module.hot.accept('./routes', () => {
    ReactDOM.unmountComponentAtNode(document.getElementById('app'));
    const nextRoutes = require('./routes').default;
    ReactDOM.render(
      <Provider store={store}>
        <Router routes={nextRoutes} history={history} />
      </Provider>,
      document.getElementById('app'),
    );
  });
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests