Tweak React components in real time.
JavaScript Shell
Clone or download
Latest commit ca3efeb Aug 3, 2018
Permalink
Failed to load latest commit information.
docs prettier Apr 18, 2018
examples Merge pull request #1037 from gaearon/mobx-example Aug 3, 2018
scripts chore: make CI stop on error (#853) Feb 10, 2018
src fix: element could be double-proxied Jul 16, 2018
test fix: element could be double-proxied Jul 16, 2018
testConfig feat: ship flat bundles (#844) Feb 7, 2018
.babelrc feat: ship flat bundles (#844) Feb 7, 2018
.eslintignore feat: ship flat bundles (#844) Feb 7, 2018
.eslintrc.js refactor(react-hot-loader): remove global Dec 28, 2017
.gitattributes Fixed test execution on Windows. May 9, 2017
.gitignore feat: ship flat bundles (#844) Feb 7, 2018
.prettierignore chore: add missing entries in changelog Apr 24, 2018
.prettierrc style: prettier + eslint fixes Dec 24, 2017
.travis.yml chore: fix ci Dec 3, 2017
CHANGELOG.md chore(release): 4.3.4 Jul 25, 2018
CODE_OF_CONDUCT.md style: prettier + eslint fixes Dec 24, 2017
CONTRIBUTING.md prettier Apr 18, 2018
ISSUE_TEMPLATE.md prettier Apr 18, 2018
LICENSE docs: improve README Oct 9, 2017
PATRONS.md Update the sponsor name Feb 6, 2016
README.md add code-splitting example Jul 5, 2018
babel.js feat: ship flat bundles (#844) Feb 7, 2018
index.js fix: disable RHL when HMR is not activated (#863) Feb 18, 2018
package.json chore(release): 4.3.4 Jul 25, 2018
patch.js feat: ship flat bundles (#844) Feb 7, 2018
react-hot-loader.d.ts fix. update typescript definitions, fixes #1018 Jun 12, 2018
rollup.config.js chore: make CI stop on error (#853) Feb 10, 2018
yarn.lock react-lifecycles-compat fixes, related to #951 May 14, 2018

README.md

React Hot Loader

Build Status version Code Coverage MIT License

PRs Welcome Chat Backers on Open Collective Sponsors on Open Collective

Watch on GitHub Star on GitHub

Tweak React components in real time ⚛️⚡️

Watch Dan Abramov's talk on Hot Reloading with Time Travel.

Install

npm install react-hot-loader

Note: You can safely install react-hot-loader as a regular dependency instead of a dev dependency as it automatically ensures it is not executed in production and the footprint is minimal.

Getting started

  1. Add react-hot-loader/babel to your .babelrc:
// .babelrc
{
  "plugins": ["react-hot-loader/babel"]
}
  1. Mark your root component as hot-exported:
// App.js
import React from 'react'
import { hot } from 'react-hot-loader'

const App = () => <div>Hello World!</div>

export default hot(module)(App)
  1. Run webpack with Hot Module Replacement:
webpack-dev-server --hot

Recipes

Migrating from create-react-app

  1. Run npm run eject
  2. Install React Hot Loader (npm install --save-dev react-hot-loader)
  3. In config/webpack.config.dev.js, add 'react-hot-loader/babel' to Babel loader configuration. The loader should now look like:
  {
    test: /\.(js|jsx)$/,
    include: paths.appSrc,
    loader: require.resolve('babel-loader'),
    options: {
      // This is a feature of `babel-loader` for webpack (not Babel itself).
      // It enables caching results in ./node_modules/.cache/babel-loader/
      // directory for faster rebuilds.
      cacheDirectory: true,
      plugins: ['react-hot-loader/babel'],
    },
  }
  1. Mark your App (src/index.js) as hot-exported:
// ./containers/App.js
import React from 'react'
import { hot } from 'react-hot-loader'

const App = () => <div>Hello World!</div>

export default hot(module)(App)

Migrating from create-react-app without ejecting

Users report, that it is possible to use react-app-rewire-hot-loader to setup React-hot-loader without ejecting. Follow these code examples to repeat the approach.

TypeScript

When using TypeScript, Babel is not required, but React Hot Loader will not work (properly) without it. Just add babel-loader into your webpack configuration, with React Hot Loader plugin.

There are 2 different ways to do it.

Add babel AFTER typescript.
{
  test: /\.tsx?$/,
  use: [
    {
      loader: 'babel-loader',
      options: {
        babelrc: false,
        plugins: ['react-hot-loader/babel'],
      },
    },
    'ts-loader', // (or awesome-typescript-loader)
  ],
}

In this case you have to modify your tsconfig.json, and compile to ES6 mode, as long as React-Hot-Loader babel plugin does not understand ES5 code.

// tsconfig.json
{
  "module": "commonjs",
  "target": "es6"
}

As long you cannot ship ES6 to production, you can create a tsconfig.dev.json, "extend" the base tsconfig and use "dev" config in dev webpack build . Details for ts-loader , for awesome-typescript-loader.

{
  "extends": "./tsconfig",
  "compilerOptions": {
    "target": "es6"
  }
}

Keep in mind - awesome-typescript-loader has a built in feature (useBabel) to babelify result.

Add babel BEFORE typescript

Note: this way requires babel 7 and babel-loader@^8.0.0

{
  test: /\.tsx?$/,
  use: [
    'ts-loader', // (or awesome-typescript-loader)
    {
      loader: 'babel-loader',
      options: {
        plugins: [
          '@babel/plugin-syntax-typescript',
          '@babel/plugin-syntax-decorators',
          '@babel/plugin-syntax-jsx',
          'react-hot-loader/babel',
        ],
      },
    }
  ],
}

In this case you can compile to ES5. More about typescript and react-hot-loader

We also have a full example running TypeScript + React Hot Loader.

Parcel

Parcel supports Hot Module Reloading out of the box, just follow step 1 and 2 of Getting Started.

We also have a full example running Parcel + React Hot Loader.

Electron

You need something to mark your modules as hot in order to use React Hot Loader.

One way of doing this with Electron is to simply use webpack like any web-based project might do and the general guide above describes. See also this example Electron app.

A webpack-less way of doing it to use electron-compile (which is also used by electron-forge) - see this example. While it requires less configuration, something to keep in mind is that electron-compile's HMR will always reload all modules, regardless of what was actually edited.

Source Maps

If you use devtool: 'source-map' (or its equivalent), source maps will be emitted to hide hot reloading code.

Source maps slow down your project. Use devtool: 'eval' for best build performance.

Hot reloading code is just one line in the beginning and one line at the end of each module so you might not need source maps at all.

Preact

React-hot-loader should work out of the box with preact-compat, but, in case of pure preact, you will need to configure it:

  • create configuration file (setupHotLoader.js)
import reactHotLoader from 'react-hot-loader'
import preact from 'preact'

reactHotLoader.preact(preact)
  • dont forget to import it

Preact limitations

  • HOCs and Decorators as not supported yet. For Preact React-Hot-Loader v4 behave as v3.

React Native

React Native supports hot reloading natively as of version 0.22.

Using React Hot Loader with React Native can cause unexpected issues (see #824) and is not recommended.

Code Splitting

If you want to use Code Splitting + React Hot Loader, the simplest solution is to pick one of our compatible library:

If you use a non-yet-friendly library, like react-async-component you have to mark all your "loaded components" as hot-exported:

// AsyncHello.js
import { asyncComponent } from 'react-async-component'

// asyncComponent could not `hot-reload` itself.
const AsyncHello = asyncComponent({
  resolve: () => import('./Hello'),
})

export default AsyncHello
// Hello.js
import { hot } from 'react-hot-loader'

const Hello = () => 'Hello'

export default hot(module)(Hello) // <-- module will reload itself

Checking Element types

Because React Hot Loader creates proxied versions of your components, comparing reference types of elements won't work:

const element = <Component />
console.log(element.type Component) // false

React Hot Loader exposes a function areComponentsEqual to make it possible:

import { areComponentsEqual } from 'react-hot-loader'
const element = <Component />
areComponentsEqual(element.type, Component) // true

Another way - compare "rendered" element type

const element = <Component />
console.log(element.type === <Component />.type) // true

// better - precache rendered type
const element = <Component />
const ComponentType = <Component />.type
console.log(element.type === ComponentType) // true

But you might have to provide all required props. See original issue. This is most reliable way to compare components, but it will not work with required props.

Another way - compare Component name.

Not all components has a name. In production displayName could not exists.

const element = <Component />
console.log(element.displayName === 'Component') // true

For Components you might be able to use instanceof operator

const element = <Component />
console.log(element.type instanceof Component) // true

This is something we did not solve yet.

Webpack ExtractTextPlugin

webpack ExtractTextPlugin is not compatible with React Hot Loader. Please disable it in development:

new ExtractTextPlugin({
  filename: 'styles/[name].[contenthash].css',
  disable: NODE_ENV !== 'production',
})

Disabling a type change (❄️)

It is possible to disable React-Hot-Loader for a specific component, especially to enable common way to type comparison. See #991 for the idea behind ⛄️, and #304 about "type comparison" problem.

import { cold } from 'react-hot-loader';

cold(SomeComponent) // this component will ignored by React-Hot-Loader
<SomeComponent />.type === SomeComponent // true

If you will update cold component React-Hot-Loader will complain (on error level), and then React will cold-replace Component with a internal state lose.

Reach-Hot-Loader: cold element got updated

Disabling a type change for all node_modules

You may cold all components from node_modules. This will not work for HOC(like Redux) or dynamically created Components, but might help in most of situations, when type changes are not welcomed, and modules are not expected to change.

import { setConfig, cold } from 'react-hot-loader'
setConfig({
  onComponentRegister: (type, name, file) =>
    file.indexOf('node_modules') > 0 && cold(type),
})

! To be able to "cold" components from 'node_modules' you have to apply babel to node_modules, while this folder is usually excluded. You may add one more babel-loader, with only one React-Hot-Loader plugin inside to solve this.

API

hot(module, options)

Mark a component as hot.

import { hot } from 'react-hot-loader'

const App = () => 'Hello World!'

export default hot(module)(App)

AppContainer

Mark application as hot reloadable. Prefer using hot helper.

import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './containers/App'

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root'),
  )
}

render(App)

// webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./containers/App', () => {
    // if you are using harmony modules ({modules:false})
    render(App)
    // in all other cases - re-require App manually
    render(require('./containers/App'))
  })
}

areComponentsEqual(Component1, Component2)

Test if two components have the same type.

import { areComponentsEqual } from 'react-hot-loader'
import Component1 from './Component1'
import Component2 from './Component2'

areComponentsEqual(Component1, Component2) // true or false

setConfig(config)

Set a new configuration for React Hot Loader.

Available options are:

  • logLevel: specify log level, default to "error", available values are: ['debug', 'log', 'warn', 'error']
  • pureSFC: enable Stateless Functional Component. If disabled they will be converted to React Components. Default value: false.
import { setConfig } from 'react-hot-loader'

setConfig({ logLevel: 'debug' })

Migrating from v3

AppContainer vs hot

Prior v4 the right way to setup React Hot Loader was to wrap your Application with AppContainer, set setup module acceptance by yourself. This approach is still valid but only for advanced use cases, prefer using hot helper.

React Hot Loader v3:

// App.js
import React from 'react'

const App = () => <div>Hello world!</div>

export default App
// main.js
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './containers/App'

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root'),
  )
}

render(App)

// webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./containers/App', () => {
    // if you are using harmony modules ({modules:false})
    render(App)
    // in all other cases - re-require App manually
    render(require('./containers/App'))
  })
}

React Hot Loader v4:

// App.js
import React from 'react'
import { hot } from 'react-hot-loader'

const App = () => <div>Hello world!</div>

export default hot(module)(App)
// main.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './containers/App'

ReactDOM.render(<App />, document.getElementById('root'))

No patch required

Code is automatically patched, you can safely remove react-hot-loader/patch from your webpack config.

Error reporter is gone

React supports error handling out of the box since v16 using componentDidCatch. You can create your own Error Boundary and install it after hot has been applied:

import React from 'react'
import { hot } from 'react-hot-loader'
import ErrorBoundary from './ErrorBoundary'

const App = () => (
  <ErrorBoundary>
    <div>Hello world!</div>
  </ErrorBoundary>
)

export default hot(module)(App)

Known limitations and side effects

Not about hot

hot accepts only React Component (Stateful or Stateless), resulting the HotExported variant of it. The hot function will setup current module to self-accept itself on reload, and will ignore all the changes, made for non-React components. You may mark as many modules as you want. But HotExportedComponent should be the only used export of a hot-module.

Note: Please note how often we have used exported keyword. hot is for exports.

Note: Does nothing in production mode, just passes App through.

New Components keep executing the old code

There is no way to hot-update constructor code, as result even new components will be born as the first ones, and then grow into the last ones. As of today, this issue cannot be solved.

Troubleshooting

If it doesn't work, in 99% of cases it's a configuration issue. A missing option, a wrong path or port. webpack is very strict about configuration, and the best way to find out what's wrong is to compare your project to an already working setup, check out examples, bit by bit.

If something doesn't work, in 99% of cases it's an issue with your code. The Component didn't get registered, due to HOC or Decorator around it, which is making it invisible to the Babel plugin or webpack loader.

We're also gathering Troubleshooting Recipes so send a PR if you have a lesson to share!

Switch into debug mode

Debug mode adds additional warnings and can tells you why React Hot Loader is not working properly in your application.

import { setConfig } from 'react-hot-loader'
setConfig({ logLevel: 'debug' })

Contributors

This project exists thanks to all the people who contribute. Contribute. contributors

Backers

Thank you to all our backers! 🙏 Become a backer backers

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Become a sponsor

License

MIT