There is a hacky workaround in https://github.com/gaearon/react-hot-boilerplate/pull/61#issuecomment-211504531 but it鈥檚 way too hacky. We should either implement this in https://github.com/reactjs/react-router/issues/2182, or include some sort of hacky workaround right in this project so at least it鈥檚 on us. Special casing React Router doesn鈥檛 sound very great but it鈥檚 hugely popular and we can鈥檛 just not support it.
3.0.0-alpha.12)I ran into this today when using react router properly for the first time, and was surprised that no-one had tackled this before.
How do all those people work with a Router that won't hot reload without getting really frustrated?
Ideally, the proper fix for this should be in resolving https://github.com/reactjs/react-router/issues/2182 - bcause the warning here is just cheeky: https://github.com/reactjs/react-router/blob/master/modules/Router.js#L131 "Sorry, this looks like a declarative component but it's actually an imperative API call and we're doing to ignore your new routes!"
I'll take my further comments / help over to that thread now 馃槃
Previous versions of React Hot Loader or React Transform used to wrap class in the proxy during its definition time, so this wasn鈥檛 much of an issue鈥攖he update would just never propagate up to the router. The current approach has many more upsides (e.g. works with functional components) but it now wraps components in createElement() so now it needs all components in chain to update the React way, so the Router behavior becomes problematic.
Thanks for looking at it!
I just had an idea I wanted to write down, not thought through heavily yet.
What if instead of trying to patch <Router>, you patch <Route> to apply the proxy code.
Something along these lines:
// Make a note of the Route component so we can detect it later
require('react-hot-loader/patch-router')(Route);
// ...
React.createElement = function(type, props, ..children) {
// as well as all the other stuff
// When we see a Route, proxy its component
if (type == Route && props.component) props.component = proxy(props.component)
// ...
}
This sort of approach would ensure that the component is proxied whenever the routes list is "rendered" even though those <Route> children are never actually applied to <Router>
Exactly how and when to do this is up for debate, but I think the general idea of opt-in to detecting Route and converting those components to proxies should sort things out.
@glenjamin
Thanks for the idea! I added this to 3.0.0-alpha.12 so people can get started more quickly. This is still ugly though so we鈥檇 better figure out a proper way to do this before the release. The router console error stays so hopefully this will bring more light to https://github.com/reactjs/react-router/issues/2182 馃槃 .
Will have to figure out some way to get this working with getComponent as well
I feel this is out of scope of this hack and should be fixed in https://github.com/reactjs/react-router/issues/2182.
@gaearon This may be a hack, but passing Router a random key seems to work fine.
// Root.js
import React, { Component } from 'react';
import { Provider } from 'react-redux'
import { browserHistory, Router} from 'react-router'
import configureStore from './store/configureStore'
import routes from './routes'
export default class Root extends Component {
render() {
const store = configureStore()
return (
<Provider store={store}>
<Router history={browserHistory} routes={routes} key={Math.random()}/>
</Provider>
);
}
}
@jaredpalmer As far as I can see, this means a different component is mounted every time, so the state of any children components is destroyed. If this works well for you (e.g. you don鈥檛 use local state a lot), you can remove React Hot Loader and just use Webpack HMR API without it.
Aside from the annoying warning, should be fixed in v3.0.0-beta.1.
Is there a working example for react-router somewhere?
@burkhardr
export default function Root() {
return (
<Router component={App}>
...
</Router>
)
}
Take this file and replace App with Root.
Sorry if this is a dense question, but is beta.1 expected to still be broken for async routes? I've been trying to make the switch from react-transform-hmr in a project of mine, and I'm currently in a state where hot reloading works for individual components but not the asynchronously-loaded views (even with Webpack code splitting disabled).
It鈥檚 expected to work (as in, I don鈥檛 see why it wouldn鈥檛), but I have not tested it, so issues might still exist.
I would appreciate if you filed an issue with a link to the branch I can test and instructions to reproduce.
Is React Router supposed to work with beta.2 with no errors? If so, what is the way of having it work? The hot reload works for me, but I keep receiving the error in console. The error is:
Warning: [react-router] You cannot change <Router routes>; it will be ignored
[EDIT] The only way to make it work is via setting <Router key={Math.random()} />
The hot reload works for me, but I keep receiving the error in console. The error is:
It is not an error, it is a warning. There is no harm in it. Yes, it鈥檚 expected to appear until React Router fixes https://github.com/reactjs/react-router/issues/2182, but you don鈥檛 need to worry about it.
[EDIT] The only way to make it work is via setting
<Router key={Math.random()} />
This does not make it work. You are forcing the root component to remount, thereby losing _all_ the local state. If this works well for you, you don鈥檛 need React Hot Loader at all because its only purpose is to preserve the local state.
Thanks @gaearon , I already got accustomed to my red friend in the console;). Just for the peace f mind it would be great to have warning displayed as warning, not error, but that's no biggie. I'll wait for RR fix. Thanks for your answer!
馃憤
EDIT: After pulling my repo and doing a clean run, I cannot reproduce the effect unfortunately.
Perhaps I'm missing something, but I seem to have things working without using @jaredpalmer's key={Math.random()} hack and I receive no warnings.
I'm using:
"react": "^15.0.1",
"react-dom": "^15.0.1",
"react-hot-loader": "^3.0.0-beta.2",
"react-router": "^2.6.0",
"webpack": "^2.1.0-beta.20",
"webpack-dev-server": "^2.1.0-beta.0"
The only change I made was instead of using a random key, I set the router's key to a static string.
Basically as you see here: https://github.com/jaredpalmer/react-production-starter/blob/master/client/index.js#L39
Just replace it with <Router routes={routes} history={browserHistory} key="ROUTER" />
Mind you, I'm using async routes using Webpack 2's native module dynamic loading with System.import for most paths.
I'll post my .babelrc and a simplified Webpack config later for others to reproduce.
Defining a static key has no effect
What seems to work is moving the routes var to a higher scope and then call setTimeout on the hot reload render.
https://gist.github.com/frankleng/39e5953b78d42fdc070eced38bd2b307
@frankleng You're right. It seems it was just a fluke that I cannot reproduce on a clean setup.
EDIT: @frankleng Trying your method, the routes never change after updating. Does it not warn you when adding or removing a route, then navigating to it using browserHistory?
It also doesn't appear you are using AppContainer in that gist, so if it does work, is local state maintained?
@chase thanks for catching that. might've missed it when I copy and pasted.
I wrap <AppContainer> around react-redux <Provider>
local state is is maintained and not seeing any warnings at the moment.
After quite a bit of searching, I found @dferber90's React Router with RHL3 compatibility table
Unfortunately, as it says, asynchronous routes still aren't functional. I did discover a clever solution to my synchronous routes not working. You can find the reference code inside of his compatibility testing fork: https://github.com/dferber90/react-hot-boilerplate/blob/next/src/routes.js#L13
Using Object.assign on an empty exported reference object bypasses the React Router equivalency checks while still permitting the routes to update.
My setup is more or less like this:
refEqualRootRoute.js
export default {}
routes.js
import App from './App'
import SomeContainer from './SomeContainer'
import refEqualRootRoute from './refEqualRootRoute'
export default Object.assign(refEqualRootRoute, {
component: App,
path: '/',
childRoutes: [
{
path: '/SomePath',
component: SomeContainer,
},
],
indexRoute: {component: App},
});
Root.js
import React from 'react';
import routes from './routes';
import {Router, browserHistory} from 'react-router/es6';
const Root = ({store}) => (
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
);
Root.propTypes = {
store: PropTypes.object.isRequired,
};
export default Root;
index.js
import {configureStore} from './store';
import rootSaga from './Sagas';
import Root from './Root';
const store = configureStore({});
let rootTask = store.runSaga(rootSaga);
const root = document.getElementById('app');
const render = () => {
ReactDOM.render(
<AppContainer>
<Root store={store} />
</AppContainer>,
root
);
};
render();
if (process.env.NODE_ENV === 'development') {
if (module.hot) {
module.hot.accept('./Sagas', () => {
rootTask.cancel();
rootTask = store.runSaga(rootSaga);
render();
});
module.hot.accept('./Root', render);
}
}
Seems like we shouldn't have any more dreaded "can't change routes" warnings with RR v4. And routes should hot reload too. https://react-router-website-uxmsaeusnn.now.sh/basic
Crazy excited for it.
When working on React Router v4 server side rendering, here's what I did on the client side:
import { AppContainer } from 'react-hot-loader';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router';
import RootLayout from './components/layouts/RootLayout';
const renderClient = () => {
ReactDOM.render(
<AppContainer>
<BrowserRouter>
<RootLayout />
</BrowserRouter>
</AppContainer>,
document.getElementById('app')
);
}
// Render the client-side DOM
renderClient();
// Enable Hot Module Replacement (HMR)
if (module.hot) {
module.hot.accept('./components/layouts/RootLayout', () => {
const nextRootLayout = require('./components/layouts/RootLayout');
renderClient();
});
}
However, for some reason, having the const nextRootLayout = require('./components/layouts/RootLayout'); there (even tho I don't use it) got it to hot re-load appropriately, but when I remove the nextRootLayout, it stops updating, not sure what's happening lol, but i'm just going to keep nextRootLayout there for now.
@codetony25 you need to use render client with your updated component
renderClient(nextRootLayout)
@jaredpalmer I had tried everything.. my modules were rebuilding, but the page wouldn't update. Your quick fix to add a random key to routes did the trick!
@jgentes as @gaearon said, that will remount all of your components on every update, so state will be lost.
thanks @calesce , that's good to know. I was refreshing the page anyway, so it's still an improvement for me.
Is this issue still on the roadmap for RR3?
@schickling It was tracked in https://github.com/ReactTraining/react-router/issues/2182, but it looks like they want everyone to migrate to 4.0 as a solution, and the maintainers won't be doing major updates on 3.X.
RR 2/3 mostly works with React Hot Loader as it currently stands, AFAIK the only issues are the console warning and getComponent not re-running.
Thanks for that clear wrapup @calesce!
RR 2/3 mostly works with React Hot Loader as it currently stands, AFAIK the only issues are the console warning and getComponent not re-running.
Anyone else having issues with RR3 and
@Codelica I "solved" it like this, with routes in their own module:
let routes = <Route>
// All your routes
</Route>
// Any update within the app will bubble up to this file
// We can't create a new <Route> element though, because Router won't accept
// updates to its routes prop.
// So we're passing the previous export from update to update to prevent Router from complaining
// The React components themselves will still update properly
if (module.hot) {
let oldRoutes = module.hot.data && module.hot.data.routes
if (oldRoutes) {
routes = oldRoutes
}
module.hot.dispose(function(data){
data.routes = routes
})
}
export default routes
@chase is your method here above https://github.com/gaearon/react-hot-loader/issues/249#issuecomment-236790396 still the recommended approach to handling redux-saga?
@awitherow I wouldn't say it is recommended, as I haven't used it with RR4 and stopped using RHL due to flaky behavior. It would not hurt to try and then post your results, though.
I just wanted to share this fairly dirty workaround for hot-reloading async routes.
Like others, I had initially added a random key to my router which blew away state every time anything hot reloaded.
So I poked around in react-router's Router.js by exposing a global reference to it then and tried recreating the internal transitionManager and router objects after a hot reload. This got things working, so I've wrapped react-router with another component that does this teardown and setup any time the routes prop changes.
I know it's bad to call these lifecycle functions directly but it's working for now, is there any other reason why I shouldn't do it?
import React, {Component} from 'react'
import {Router, browserHistory} from 'react-router'
export default class HackyRouter extends Component {
componentDidMount(){
// componentWillReceiveProps just whines about changing the routes prop so this shuts that up
this.router.componentWillReceiveProps = function(){}
}
componentDidUpdate(prevProps){
if(prevProps.routes != this.props.routes){
// tear down and set up router internals again
this.router.componentWillUnmount()
this.router.componentWillMount()
}
}
render(){
return <Router ref={ref=>this.router = ref} history={browserHistory} routes={this.props.routes} />
}
}
import React from 'react'
import {render} from 'react-dom'
import HackyRouter from './hacky_router'
import routes from './routes'
import {AppContainer} from 'react-hot-loader'
const root = document.getElementById('root')
render((<AppContainer><HackyRouter routes={routes} /></AppContainer>), root)
if (module.hot) {
module.hot.accept('./routes', () => {
// reload the routes file
let nextRoutes = require('./routes')
render((<AppContainer><HackyRouter routes={nextRoutes} /></AppContainer>), root)
})
}
We still reproduce the issue with react-router and redux, in the console we have WDS] App hot update...
dev-server.js?05e4:45 [HMR] Checking for updates on the server...
log-apply-result.js?e2ce:20 [HMR] Updated modules:
log-apply-result.js?e2ce:22 [HMR] - ./components/Login/LoginForm.tsx
log-apply-result.js?e2ce:22 [HMR] - ./components/Login/LoginModal.tsx
log-apply-result.js?e2ce:22 [HMR] - ./containers/App/index.tsx
log-apply-result.js?e2ce:22 [HMR] - ./containers/AppComponent/index.tsx
dev-server.js?05e4:27 [HMR] App is up to date. but browser doesn't re-render anything?
Same here, it says App is up to date but the DOM never changes.
All the latest versions:
And configuration from here: https://webpack.js.org/guides/hmr-react/
Has there been any progress on this? Also getting a similar issue where the CSS hot reloads 馃檶 however the WDS tells me the app is up to date, but no DOM changes are made.
"react-router": "^2.6.1",
"webpack": "3.5.5",
"webpack-dev-server": "^2.7.1"
@aaronatmycujoo react-router < 4 will not be supported, if you use it you can setup hot reload stuff in Webpack without React Hot Loader. Your app will be entirely refreshed when a change occurs.
Still having this issue with
"react": "^15.6.1",
"react-hot-loader": "^3.1.2",
"react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"redux": "^3.7.2",
"webpack-dev-server": "^2.7.1"
Any update? Why was this issue closed? @gaearon @wkwiatek
@tgroutars - cos https://github.com/ReactTraining/react-router/issues/2182#issuecomment-216220598 and https://github.com/ReactTraining/react-router/issues/2182#issuecomment-246901302
@theKashey I had tried everything.. my modules were rebuilding, but the page wouldn't update. Is there a working example for react-routera and redux somewhere?
_Good_ example will got help you - it should work out of the box. Better provider your example to play with.
Most helpful comment
@gaearon This may be a hack, but passing
Routera randomkeyseems to work fine.