A summary of the differences between Webpack HMR and React-Hot-Loader
I've frequently seen people get confused while trying to set up Webpack's Hot Module Replacement (HMR) capabilities for use in a React application. The most common source of confusion is the assumption that they must use the React-Hot-Loader tool to turn on HMR. That's not the case, and I'd like to clarify the differences.
Hot Module Replacement is a core capability offered by Webpack. Webpack's compiler offers a
module.hot.accept() API. Your application code can register callbacks to run when certain files have been recompiled. Here's how the process works:
- Your Webpack config needs to include the HMR client code as an entry point in addition to your actual application entry file. This adds a small piece to the client bundle, which will open a websocket connection back to the Webpack dev server.
- Your client application code calls
- Webpack's dev server watches your files for changes, and recompiles source files when you hit save
- The dev server sends a message to the client, announcing that those files have been recompiled, and including the new versions of the code
- The client runs any
module.hot.acceptcallbacks that match that file's path
What happens in that callback is up to you, but normal usage is to re-import the changed file and swap out the affected code, such as a React component or a Redux reducer. If you use this "plain HMR" approach in your own application, you would generally only have one or two calls to
module.hot.accept(), such as one for your root component file and one for your root Reducer file. I show an example of using "plain HMR" in my post Practical Redux, Part 3: Project Setup.
It's important to note that none of this requires the React-HotLoader tool!. This only requires Webpack (and to be nitpicky, I believe Browserify can be tweaked to offer HMR as well).
React-Hot-Loader uses the Webpack HMR API in a very powerful, but complex way. RHL transforms your code as it is compiled to add "proxy" components that wrap around anything that looks like a React component. Those "proxy" components insert their own
module.hot.accept() calls at a much finer-grained level, to try to catch updates to each specific React component file instead of letting the updates "bubble up" to the root component file. The "proxy" components also try to manage the component state for the "real" components, so that when the "real" components get swapped out, the component state is carried over.
RHL is great... when it works. Unfortunately, there's a lot of edge cases involved, and that causes a lot of problems. Variations in build config can cause RHL to not work right. This is one of the reasons why Dan Abramov originally stopped working on RHL and started Create-React-App - to provide a stable, consistent, and known build environment as a potential baseline for later revisiting the idea of making advanced hot reloading widely available.
I personally advise that people not use React-Hot-Loader, and instead stick with "plain HMR". With "plain HMR", updates to your component tree will not keep component state between reloads, but the implementation is much simpler and more reliable. Also, Redux goes great with plain HMR, as any data that's kept in Redux is still there when you reload the component tree from edits, and so the app will re-render based on that Redux state.
This is a post in the Blogged Answers series. Other posts in this series:
- Aug 02, 2017 - Blogged Answers: Webpack HMR vs React-Hot-Loader
- Dec 18, 2017 - Blogged Answers: Resources for Learning React
- Dec 18, 2017 - Blogged Answers: Resources for Learning Redux
- Mar 29, 2018 - Blogged Answers: Redux - Not Dead Yet!
- Jan 19, 2019 - Blogged Answers: Debugging Tips
- Jul 10, 2019 - Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns
- Nov 26, 2019 - Blogged Answers: Learning and Using TypeScript as an App Dev and a Library Maintainer