Closed gaearon closed 5 years ago
Is it possible to get this working with react-router's async routes and webpack code splitting using match
on the client as described in: react-router/ServerRendering.md
I have tried without success. Any help is appreciated.
@chrisblossom No, this hack only works for sync routes. The only solution to hot reloading with async routes is fixing https://github.com/reactjs/react-router/issues/2182.
Great improvement, I'm finally able to use react hot-reload from Haxe, another compile-to-JS language.
Only limitation that I found: Haxe JS generation doesn't go through a loader, so I'm only using react-hot-loader/webpack
to resolve the requires
. It all works perfectly excepted source mapping: our JS has a source map but it's lost in the process.
@elsassph Do you mind filing an issue in React Hot Loader? Preferably with a minimal reproducing project.
@chrisblossom No, this hack only works for sync routes. The only solution to hot reloading with async routes is fixing reactjs/react-router#2182.
@gaearon Thanks for the info. I ended up disabling code splitting in development to keep HMR working.
This thread is irrelevant with RHL tech details, but just some noisy whining.
Really appreciate your effort to make our development flow much more awesome, but I'll be even more grateful if you can point out that RHL@3.0.0-alpha doesn't work with React Router currently in your OP (even there are lots of pending hacks to be reviewed and merged).
Initially I'm thrilled to know functional components hot reloading is finally supported, and just can't wait to upgrade and refactor a redux project which finished three or four months ago (in which react-router-redux
was redux-simple-router
).
After hours of effort to upgrade dependency and set up environment, I failed to enable hot reloading in my project, totally. I mean before upgrading, at least the hot reloading is working but just some error thrown when related with functional components. Now, it's totally not working.
I know it's my fault that I haven't read through all these conversations before upgrade and refactor, but in your OP there're too many flowers and fireworks and plus ones, it's really hard to stay cool and be suspicious.
Anyway, hot reloading is still awesome, but I'll definitely wait for the final release this time.
@jasonslyvia I too got stuck initially with getting this to work with Router Router. Turns out the magic trick is to force the Router to unmount and mount after a hot reload. See https://github.com/rybon/counter-hmr/blob/master/src/components/Root.jsx (the hmrKey
bit). The actual HMR implementation is just as Dan described in this TodoMVC commit, see https://github.com/rybon/counter-hmr/blob/master/src/index.jsx.
This repo is a small example, but uses all the major libraries for demonstration purposes. Check out the files in the root folder (package.json
, .babelrc
, webpack.config.js
, server.js
). I removed as many dependencies as I could from package.json
to demonstrate that one should only need a few devDependencies to get it to work.
Another thing worth noting is that the routes module should only export the routes, like so https://github.com/rybon/counter-hmr/blob/master/src/routes.jsx#L5. If you export other stuff from there as well I found the HMR fails, probably because it can't resolve the dependency graph properly anymore. I solved that issue in my other apps by moving these additional exports (such as constants for route paths) to a separate file and importing from there instead.
@rybon Thank you for your solution but adding key
to Router
causes all sub-components' local state removed, so I guess that's not ideal.
@jasonslyvia that is why you should not be using local state :). I know it is convenient and tempting, but I found a lot of issues simply go away if you store everything in the Redux store, even state one would consider 'local' for a certain component (such as a toggle boolean to show/hide something), and keep the components stateless.
Same goes for triggering side effects (setTimeout
, XHR
etc.) in component lifecycle methods. Again, very convenient and tempting, but keeping those things out of the dispatch -> rerender loop helps with achieving the goal of making the UI a pure and idempotent function of application state ((state) => UI
).
This is why functional stateless components are great! They enforce these constraints.
@rybon I guess that's off the topic and if I don't have any local state, I don't even have to use react-hot-loader at all, just webpack's module.hot
API is sufficient.
I guess that's off the topic and if I don't have any local state, I don't even have to use react-hot-loader at all, just webpack's module.hot API is sufficient.
This is correct. One of the points of RHL 3 is the process is “progressively enhancing”. You set up HMR first, then if you want to preserve the state, you do a few extra steps, can always go back easily if RHL causes problems.
@rybon
This is not correct. If you put a unique key you might as well not use React Hot Loader at all because vanilla HMR works exactly the same way.
@jasonslyvia
I'm sorry this did not work for you yet. As you said, there are more pending fixes that may fix the problem for you later. Those are still workarounds because the underlying problem is with React Router itself. It doesn't behave the way any other components do: it doesn't accept new props. We hope to fix it on the router side but please understand this takes some effort, and React Hot Loader is a hobby project. If you want an issue to be fixed, it might be a good idea to take some time to look into it yourself and help diagnose it, or at the very least create a minimal project reproducing it so that other people can look at it later.
Really appreciate your effort to make our development flow much more awesome, but I'll be even more grateful if you can point out that RHL@3.0.0-alpha doesn't work with React Router currently in your OP
Also I'm afraid this is wrong. It does work. Currently the limitation is that only JSX config with sync routes works. But it's incorrect to say it doesn't work at all.
I will amend the OP post to include this clarification.
in your OP there're too many flowers and fireworks and plus ones, it's really hard to stay cool and be suspicious.
I'm sorry that people are enthusiastic about this alpha version which is how it is tagged and called. The reason it is released as an alpha is so that the community can help find issues like this.
After hours of effort to upgrade dependency and set up environment, I failed to enable hot reloading in my project, totally.
In the future I suggest that if you can't afford to spend these hours, don't use alpha versions of experimental packages. ;-)
I added “Known Issues” to the opening post.
@gaearon for my understanding, how does RHL in combination with React Router work then? In my code, I could only get it to work by using Math.random
as the <Router />
key. But you are saying that should not be necessary? If so, great!
For "Known Issues" it's probably worth it to include this as well: webpack/webpack#2399, as export default function
is a common pattern for functional components which React Hot Loader 3 supports.
Saddly I'm using POJO React Router config because it's a rather huge and complex project with many routes. Anyway I guess it's my fault to blindly upgrade and refactor despite the fact of alpha version.
And I have to say that @gaearon you're so productive and responsive, that's why I love the open source community.
Many thanks!
@jasonslyvia
No problem. I just released 3.0.0-alpha.13
which should work with POJO routes.
@rybon
In your App.js
file (whatever your root component is called), export a root component like this:
export default function Root() {
return (
<Router>
...
</Router>
)
}
Then use it normally in <AppContainer>
, i.e. <AppContainter component={Root} />
.
As of 3.0.0-alpha.13
, this should work with any way of configuring React Router except async routes.
@gaearon you're right, it does work without the unique key hack. I've updated my example counter-hmr app accordingly. Thanks for the help!
Maybe I'm missing something but 3.0.0-alpha.13 still not working for me.
// app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { syncHistoryWithStore } from 'react-router-redux';
import configureStore from './redux/configureStore';
import { AppContainer } from 'react-hot-loader';
import { browserHistory } from 'react-router';
const store = configureStore();
syncHistoryWithStore(browserHistory, store);
const Root = require('./containers/Root').default;
ReactDOM.render(
<AppContainer component={Root} props={{
store,
}} />
, document.getElementById('app'));
if (module.hot) {
module.hot.accept('./containers/Root', () => {
ReactDOM.render(
<AppContainer component={require('./containers/Root').default}
props={{
store,
}}
/>
, document.getElementById('app'));
});
}
// Root.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router';
import DevTools from './DevTools';
import routes from '../routes';
import { browserHistory } from 'react-router';
const Root = ({ store, history }) => {
return (
<Provider store={store}>
{
__DEV__ ?
(<div>
<Router history={browserHistory} routes={routes} />
<DevTools />
</div>) :
<Router history={browserHistory} routes={routes} />
}
</Provider>
);
};
export default Root;
And whenever I edited some component, I get the following output.
[HMR] Waiting for update signal from WDS...
[WDS] Hot Module Replacement enabled.
[WDS] App updated. Recompiling...
[WDS] App hot update...
[HMR] Checking for updates on the server...
[react-router] You cannot change <Router routes>; it will be ignored
[HMR] Updated modules:
[HMR] - 1363
[HMR] - 1362
[HMR] - 1361
[HMR] - 1538
[HMR] - 1360
[HMR] App is up to date.
Predictably the component is not hot reloaded. Here are some dependencies I'm using:
"react": "~0.14.3",
"react-dom": "~0.14.3",
"react-redux": "~4.4.5",
"react-router": "~2.3.0",
"react-router-redux": "~4.0.4",
"redux": "^3.5.2",
"react-hot-loader": "3.0.0-alpha.13",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"babel": "^6.5.2",
"babel-core": "^6.7.7",
"babel-loader": "^6.2.4",
Can anyone point out where did I go wrong?
@jasonslyvia I'd say your code looks mostly OK, although you are using browserHistory
instead of the passed in history
in the Root
component. syncHistoryWithStore(browserHistory, store);
should be const history = syncHistoryWithStore(browserHistory, store);
. Then, pass history
in as a prop in your AppContainer
.
Could you verify my app https://github.com/rybon/counter-hmr works on your end? If so, I'd check what the differences between your code and mine are and see if you can resolve them. Perhaps you can wrap routes
in a function and render like so:
<Router history={history} />
{getRoutes()}
</Router>
If nothing helps, npm cache clean
rm -rf node_modules
npm install
? Update to the latest dependencies in your package.json
?
In any case providing a complete project that reproduces the issue would be most helpful.
React Hot Loader 3 beta is out. Release notes: https://github.com/gaearon/react-hot-loader/releases/tag/v3.0.0-beta.0.
@bkgunby Was your problem solved? I see your message in my email but I don't see it here :disappointed:
Never mind. There is a serious issue when used together with React Redux: https://github.com/gaearon/react-hot-loader/issues/266. The good news is, it’s fairly easy to solve, and solving it might also solve full RR support.
@gaearon Is this support to be working now with react router POJO static routes?
POJO routes are already working in the current beta. The only thing that doesn't work today is async routes.
This is what will get fixed together with https://github.com/gaearon/react-hot-loader/issues/266 in the next beta (not solved yet).
@gaearon hmm. Not working for me (SystemJS), Ill try play a bit more and maybe get a repo up to replicate. Does it need the react router hack that was posted earlier? Or should it work without monkey patching React Router
It should work fine as is, but it is necessary that the root component gets rerendered. Just replacing the module won't be enough.
If you can post an example I can take a look.
v3.0.0-beta.1
fixes a few major issues. React Router support should now also be complete.
Please update.
https://github.com/gaearon/react-hot-loader/releases/tag/v3.0.0-beta.1
@gaearon it was the same issue gaearon/react-hot-loader#266 had. But I came across your wonderful medium post on hot loading, and I realized I don't even need it for the most part lol.
Thanks for reaching out!
@bkgunby Got it, cool! I’d appreciate if you gave 3.0.0-beta.1
a try though in case you discover more problems we can fix before the stable release 😄 . Thanks!
I'm just getting into Redux, but hot replacing reducers doesn't appear to work for me (i'm on 3.0.0-beta.1
).
I've tried modifying both ../reducers/index.js
, and ../reducers/counter.js
and neither show the console.log
and state:
[HMR] The following modules couldn't be hot updated: (They would need a full reload!)
Here is my store/configureStore.js
file
import { createStore } from 'redux'
import rootReducer from '../reducers'
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState,
window.devToolsExtension ? window.devToolsExtension() : undefined)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
console.log('replacing root reducer');
const nextReducer = require('../reducers').default
store.replaceReducer(nextReducer)
})
}
return store
}
Maybe I'm missing something?
I'm just getting into Redux, but hot replacing reducers doesn't appear to work for me (i'm on 3.0.0-beta.1).
This shouldn’t be related to React Hot Loader, it has no opinion on what you do with reducers. It only handles React components. That said, if you remove RHL, and the issue goes away, please file a bug in RHL. It’s hard to say more without a complete project reproducing this issue.
@gaearon understood, thanks :)
Is there any way to get rid of the
[react-router] You cannot change <Router routes>; it will be ignored
error?
@leshow Yes, you can contribute to React Router to help fix this: https://github.com/reactjs/react-router/issues/2182
Two questions after playing with this today:
1) Does anyone have a complicated example yet using redux/redux-react-router? Something like this non-working example that works would be great:
<AppContainer>
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Root}>
<Route path="debug" component={Root} />
</Route>
</Router>
</Provider>
</AppContainer>, document.getElementById('container'));
if (module.hot) {
module.hot.accept('./components/root.jsx', () => {
ReactDOM.render(
<AppContainer
component={Root} props={{store}}
/>, document.getElementById('container'));
});
}
2) The examples I see have:
if (module.hot) {
//...
<AppContainer
component={require(./components/root)} props={{store}} />
...
Is the require
part necessary? You can't just include that already imported Root
component for this? I have been trying to be in the habit of imports vs requires but I notice this require
seems to be necessary, maybe to re-instantiate the component?
Thanks for all the hard work, @gaearon!
1) Does anyone have a complicated example yet using redux/redux-react-router? Something like this non-working example that works would be great:
Just move <Provider>...</Provider>
into a separate component called Root
in another file that looks like
export default Root() {
return <Provider>...</Provider>
}
Then you can use <AppContainer><Root /></AppContainer>
in both places.
Is the require part necessary? You can't just include that already imported Root component for this? I have been trying to be in the habit of imports vs requires but I notice this require seems to be necessary, maybe to re-instantiate the component?
It is necessary in Webpack 1.x because the Root
component will point to the old version even after a hot update. require()
ing it again retrieves the new version.
In Webpack 2, this is unnecessary because Root
binding is updated automatically. So you can just use Root
again.
Thanks @gaearon. So what am I missing here?
Passing "component" prop to
is deprecated. Replace
It's been a long day so apologies if it's simple, but if this is deprecated, how do I do:
<AppContainer component={require(./components/root)} props={{store}} />
Is this going to be acceptable behavior?
let X = require('./components/root.jsx').default;
ReactDOM.render(
<AppContainer>
<X />
</AppContainer>,
document.getElementById('container'));
});
@framerate yes, and that way you can also use props normally. <X store={ store } />
.
@framerate I have a "complicated" boilerplate for this I made last night. It uses react-router/react-router-redux and a laundry list of other things. I mashed together the minimal boilerplate example with react-slingshot
I created a compatibility table of hot-loadable components. It contains different kinds of components together with react-router. Maybe this is helpful to somebody else wondering why it sometimes works and sometimes doesn't.
The result for react-hot-loader/babel
:
Component | Hot Loading |
---|---|
Sync | |
SyncIndexRouteComponent | ✓ |
SyncRouteComponent | ✓ |
SyncRouteComponentChild | ✓ |
SyncNonExportedComponent | ✓ |
SyncReferenceModifiedComponent | ✗ |
SyncAssignedComponent | ✓ |
Aync | |
AsyncIndexRouteComponent | — |
AsyncRouteComponent | — |
AsyncRouteComponentChild | — |
After manually remounting the components by changing routes back and forth all hot-updates were applied to components which ignored updates initially (marked with —).
Legend:
Dependencies:
The repository can be found here: https://github.com/dferber90/react-hot-boilerplate.
@gaearon Is this how it is supposed to work at the moment or did I mess up the setup?
I also discovered that components in async routes are hot-loaded correctly when they are included into the application somewhere else directly (e.g. adding import './AsyncRouteComponent'
to App.js
in the example repo would make it hot-load correctly).
@dferber90
Thank you very much for creating a sample app! I added a link to it from https://github.com/gaearon/react-hot-loader/issues/288. Please feel free to help out!
For anybody interested, I have managed to set up react-hot-loader to work with react-router async routes (and redux) by manually adding some workarounds. These workaround will hopefully become unnecessary once react-hot-loader v3 is out and react-router is upgraded. Use with caution until then :)
Make sure all your components are defined with const
. This is to avoid changing the reference of a component before the export which would break react-hot-loader.
More about this in https://github.com/dferber90/react-hot-boilerplate.
Create a file referentially-equal-root-route
which only contains export default {}
. Import that file where your routes are defined and use
import referenctiallyEqualRootRoute from './referentially-equal-root-route'
const routes = Object.assign(referenctiallyEqualRootRoute, {/* your actual routes */})
// ...
render () { return <Router routes={routes} /> }
// ..
This ensures the root route has the same reference even across hot-loads and therefore avoids the warning message. This workaround can be removed once react-router accepts changing props.
I'm not sure of the consequences this approach has for onEnter
onLeave
hooks being fired, so use with caution. This step is not really required for hot-loading components of async routes (I think).
This is to enable hot-loading components of async routes. In whatever component is wrapped in AppContainer
, import the code-split points into the main application (in development).
Given you have this somewhere in your app
getChildRoutes (location, cb) {
require.ensure([], require =>
cb(null, require('./async').childRoutes))
},
Add this to your Root
component
if (process.env.NODE_ENV !== 'production') {
// ...
require('./async') // path to the same `async` file as above
}
When the main bundle contains the code-split components hot-loading them will work. Since this should only be the case in development, we only include them in dev mode.
Make sure all your components are defined with const. This is to avoid changing the reference of a component before the export which would break react-hot-loader.
Woah, good gotcha. We should definitely document this. It’s a bit of an unfortunate limitation but I don’t see this as a big issue yet. I filed https://github.com/gaearon/react-hot-loader/issues/295.
When the main bundle contains the code-split components hot-loading them will work. Since this should only be the case in development, we only include them in dev mode.
I wonder if this is intentional or not. Is this a bug in Webpack? Why doesn’t it propagate hot updates to async parents? Do you think it’s worth to raise an issue?
Woah, good gotcha
Thank you :)
I wonder if this is intentional or not. Is this a bug in Webpack? Why doesn’t it propagate hot updates to async parents? Do you think it’s worth to raise an issue?
Well, the updates seem to arrive. The app gets rerendered from the root, through the module.hot.accept
callback.
I have no idea why this fixes hot-loading, I stumbled upon this workaround while debugging. I'm not familiar with webpack internals so I can't judge where the culprit lies :(
I'm working on converting a rather complicated electron package over (and rewriting my webpack config as I go).
Is this immediately indicative of something? If I switch away from only-dev-server
it forces a full refresh but I thought the Maximum call size exceeded
might be indicative of a common mistake I missed while upgrading to hmr3. Any suggestions appreciated!
[WDS] App updated. Recompiling...
[WDS] Warnings while compiling.
./~/encoding/lib/iconv-loader.js
Critical dependencies:
9:12-34 the request of a dependency is an expression
@ ./~/encoding/lib/iconv-loader.js 9:12-34
[WDS] App hot update...
[HMR] Checking for updates on the server...
[HMR] The following modules couldn't be hot updated: (They would need a full reload!)
[HMR] - 856
[HMR] Nothing hot updated.
[HMR] App is up to date.
Uncaught RangeError: Maximum call stack size exceeded
@dferber90 thanks for your example!
Do you (or anyone here) know if it is safe to create singletons like the application store by putting an instance on export default
like you do in store.js? I couldn't find any definite answer on this. I thought hot reloading could maybe mess things up but I'd love to have a confirmation that this is ok...
A Big Update Is Coming
React Hot Loader 3 is on the horizon, and you can try it today. It fixes some long-standing issues with both React Hot Loader and React Transform, and is intended as a replacement for both.
Some nice things about it:
react-hot-loader/babel
from.babelrc
and instead addreact-hot-loader/webpack
to loaders)The docs are not there yet, but they will be added before the final release. For now, this commit is a good reference to upgrading your project from React Hot Loader 1 to React Hot Loader 3 alpha. Then see another commit as a reference for upgrading from React Hot Loader 3 alpha to React Hot Loader 3 beta.
With lessons learned both from RHL and RT, here is a demo of a unified approach.
This is really undocumented for now, and we might change API later, so feel free to play with it at your own risk. 😉
react-hot-loader/webpack
is intended to be optional. We will provide a complementaryreact-hot-loader/babel
that detects unexported components as well. You will be able to use either, depending on whether you already use Babel or not.Known Issues
AppContainer
crashes (https://github.com/gaearon/react-hot-loader/issues/283). Until fixed, a workaround is to render the app directly on the server instead.RedboxReact
sometimes fails to display an error message due to https://github.com/stacktracejs/stackframe/issues/11.React.createFactory
orReact.DOM.*
factories. It works either with JSX or withReact.createElement
. (https://github.com/gaearon/react-hot-loader/issues/277)