React - Hot Module Reload

I’ve recently revisited the Hot Module Reload idea for React to refresh the material for an upcoming seminar I’m going to hold and found out that not only the solution I was using so far (babel-preset-react-hmre) is no longer maintained, but that the suggested replacement (react-hot-loader) is getting a major version soon (3.0), and the current documentation and most linked content refers to the 1.0 branch.

This article will explain how to setup HMR with react-hot-loader in its current 3.0.0-beta 5 build, which will likely end up in the stable release.

What is Hot Module Reload?

Even though the compilation (“bundling”) process required in modern JavaScript development is nothing compared to that people running Java or other compiled languages are used to, it is still an issue that slows us down, especially if we need to refresh the app and restore or navigate back to the state we were at. Enter HMR — a technique that allows us to replace parts of our application directly in the client we’re running without losing the current state.

1. Setup

In order to make use of react-hot-loader package we need to install some required packages. This article assumes that the reader is using webpack (and webpack-dev-server) and babel, aside for the basic packages you will also need grab just the HMR package:

npm install --save-dev react-hot-loader

Next step is to load the babel variant of the loader. If you’re not using .babelrc file you might need to create one (you might also want to move your loader config there) to load the plugin:

{
"plugins": ["react-hot-loader/babel"],
"presets": ["react", "es2015"]
}

We also need to alter our webpack.config.json to make the bundler include the required libraries:

entry: [
"webpack-dev-server/client?http://localhost:8080", // WebpackDevServer host and port
"webpack/hot/only-dev-server", // "only" stops HMR on syntax errors
"react-hot-loader/patch", // make sure the HMR package is included
"./src/index" // our application entry point
],

You can move those to your ./src/index file — but this solution is automatically adding the required modules to every entry point if you decide to change it, and by having separate webpack.config.js files for dev, desting and production you can easily enable or disable HMR.

2. Changes to your app

In order for your app to work, we also need to make some changes to the entry point and ReactDOM.render call.

First thing you will want to do is make sure that you declare your root component in a separate file — make sure your entry point ( ./src/index ) is importing the root component and only calling ReactDOM.render — otherwise every change will trigger HMR but it wont update and throw a warning in the console that reads something like this:

The following modules couldn’t be hot updated: (They would need a full reload!)

I’m going to assume you named your root component <App /> for sake of this example.

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));

Second thing will be loading an AppContainer component from react-hot-loader. More on that in a minute.

import { AppContainer } from “react-hot-loader”

Third and last thing that you need to do is add some code to take care of the HMR after your initial render, so alter your file. Change your render call:

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

and add the following bellow it:

if (module.hot) { // 1
module.hot.accept("./App", () => { // 2
const NextApp = require("./App").default; // 3
ReactDOM.render(<AppContainer><NextApp /></AppContainer>,
document.getElementById("root")); // 4
});
}

Now for some explanation:

1 — module.hot is an variable made available by webpack and is truthy when the hot reload mode is used.

2 — module.hot.accept — an API function that takes the location of the dependency, which change to monitor; here we’re going to monitor change to our root component

3 — we’re going to load the component once more, because the one we loaded before is a cached copy of the old file. We’re not using import statement here, because all imports must be placed at the top of the file

4 — we render our root component once more, but we wrap it in an <AppContainer /> high order component

If we were to skip the<AppContainer /> hoc the example would at first appear to work — if we would change the rendered HTML of our components it would update on the fly. The issue is that each reload would result in the lose of state of each affected component. Using <AppContainer /> prevents that, as the component will keep and reapply the state of every child component.

3. Running the app

We’re done with the changes required to our app, all that’s left is to run it; you can do that by calling:

webpack-dev-server -hot

If your webpack config was valid before, you should be now able to open the browser and point it to the correct URL (default would be http://localhost/:8080). You can open the browser console window to see if it’s all working — check if you see the following in the output:

[HMR] Waiting for update signal from WDS…
[WDS] Hot Module Replacement enabled.

If so, go ahead and make a change to App.js — assuming no parse error you should see the change reflected in your browser as soon as a new bundle is compiled.

Closing notes

HMR is a great tool which allows us to develop the app a lot faster than manually or even automatically refreshing the app after every change. To gain additional time, you might also want to disable devtool in webpack to not generate a source map.

Remember to turn off HMR in your production app.

Sources: