Hot Reloading in Create React App Without Ejecting

By Dave Ceddia

Create React App is great, but the projects it generates don’t have Hot Module Replacement (HMR) set up by default.

Read on for how to get it working.

The Plain Webpack Way

With just 3 lines of code, you can turn on HMR, but with one big caveat: React state and DOM state are not preserved between reloads. This is kind of a bummer.

You can add these lines to index.js to turn on standard Webpack HMR that doesn’t preserve state between reloads:

if (module.hot) {
  module.hot.accept();
}

State Is Reset

When I originally wrote this article, I had been using this method and it seemed to be working. But after doing a test with hot reloading combined with a bit of state (spurred by a comment on Reddit), seeing that did not actually preserve state was disappointing.

I think the reason it appeared to work was because my app stores almost all of its state in Redux, and after a hot reload, the Redux actions were replayed by Redux DevTools, and the app was left in the same state as before the hot reload.

The react-hot-loader Way

You can “rewire” Create React App without ejecting, which will allow you to enable real hot module reloading. This relies on react-app-rewired and react-app-rewire-hot-loader, along with Dan Abramov’s react-hot-loader.

For more detail on the differences between the Webpack way and react-hot-loader, read this post by Mark Erikson.

And as they say in the react-app-rewired README…

By doing this you’re breaking the “guarantees” that CRA provides. That is to say you now “own” the configs. No support will be provided. Proceed with caution.

Here we go.

Install these 3 packages:

yarn add react-app-rewired react-app-rewire-hot-loader react-hot-loader

Create a file called config-overrides.js in the root directory of your project (not under “src”) with this code:

const rewireReactHotLoader = require('react-app-rewire-hot-loader');

module.exports = function override(config, env) {
  config = rewireReactHotLoader(config, env);
  return config;
}

Change index.js accordingly:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

// Add this import:
import { AppContainer } from 'react-hot-loader';

// Wrap the rendering in a function:
const render = () => {
  ReactDOM.render(
    // Wrap App inside AppContainer
    <AppContainer>
      <App />
    </AppContainer>,
    document.getElementById('root')
  );
};

// Do this once
registerServiceWorker();

// Render once
render();

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

Change package.json to use react-app-rewired instead of react-scripts. In the “scripts” section, change this:

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test":  "react-scripts test --env=jsdom"
}

To this:

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test":  "react-app-rewired test --env=jsdom"
}

Now when you start up the app, it will pass the Webpack config through react-app-rewired, through config-overrides.js and the rewire-hot-loader library, and then react-hot-loader and its AppContainer component will make hot reloading work properly. Give it a try!