React Hot Loader
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.
🔥 HOT-LABS 🔥
Latest (4.5.0+, beta) version of React-Hot-Loader could be quite 🔥!
RHL will patch React, replace React-DOM by React-🔥-DOM and work with fiber directly
import { setConfig } from 'react-hot-loader'
setConfig({
ignoreSFC: true,
pureRender: true,
})
Getting started
- Add
react-hot-loader/babel
to your .babelrc
:
{
"plugins": ["react-hot-loader/babel"]
}
- Mark your root component as hot-exported:
import { hot } from 'react-hot-loader/root'
const App = () => <div>Hello World!</div>
export default hot(App)
There is also old version of hot
, used prior version 4.5.4. Please use a new one,
as it is much more resilient to js errors you may make during development.
import { hot } from 'react-hot-loader'
const App = () => <div>Hello World!</div>
export default hot(module)(App)
- Run webpack with Hot Module Replacement:
webpack-dev-server --hot
Limitations
- (that's the goal) React-Hot-Loader would not change the past, only update the present - no lifecycle event would be called on component update.
As a result - all the code changes, you may made among
componentWillUnmount
or componentDidMount
, would be ignored for
already created components. - (that's the goal) React-Hot-Loader would not update any object, including component
state
. - (1%) React-Hot-Loader could not reply some changes you may made in components
constructors
. As long as
components would not be recreated - RHL have to inject new data onto existing components, but there is no way to detect the actual change and the way reply it.
React-Hot-Loader knows what class method is, not how you created it. See #1001 for details.
Recipes
- Run
npm run eject
- Install React Hot Loader (
npm install --save-dev react-hot-loader
) - 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: {
cacheDirectory: true,
plugins: ['react-hot-loader/babel'],
},
}
- Mark your App (
src/App.js
) as hot-exported:
import React from 'react'
import { hot } from 'react-hot-loader'
const App = () => <div>Hello World!</div>
export default hot(module)(App)
Users report, that it is possible to use react-app-rewire-hot-loader to setup React-hot-loader without ejecting.
TypeScript
As of version 4, React Hot Loader requires you to pass your code through Babel to transform it so that it can be hot-reloaded. This can be a pain point for TypeScript users, who usually do not need to integrate Babel as part of their build process.
Fortunately, it's simpler than it may seem! Babel will happily parse TypeScript syntax and can act as an alternative to the TypeScript compiler, so you can safely replace ts-loader
or awesome-typescript-loader
in your Webpack configuration with babel-loader
. Babel won't typecheck your code, but you can use fork-ts-checker-webpack-plugin
(and/or invoke tsc --noEmit
) as part of your build process instead.
A sample configuration:
{
resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] },
module: {
rules: [
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
cacheDirectory: true,
babelrc: false,
presets: [
[
"@babel/preset-env",
{ targets: { browsers: "last 2 versions" } }
],
"@babel/preset-typescript",
"@babel/preset-react"
],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
"react-hot-loader/babel"
]
}
}
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin()
]
};
For a full example configuration of TypeScript with React Hot Loader and newest beta version of Babel, check here.
As an alternative to this approach, it's possible to chain Webpack loaders so that your code passes through Babel and then TypeScript (or TypeScript and then Babel), but this approach is not recommended as it is more complex and may be significantly less performant. Read more discussion here.
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)
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.
Webpack plugin
We recommend to use babel
plugin, but there are situations when you are unable to use it, then - try webpack loader (as seen in v3)
to have at least something.
Remember - it is not compatible with class-based components - as long as babel plugin
would inject a special methods to the every class, to make class members
(like onClick) hot-updatable,
while webpack-plugin would leave classes as is, without any instrumentation.
class MyComponent extends React.Component {
onClick = () => this.setState()
variable = 1
render() {}
}
But webpack-loader could help with TypeScript or spreading "cold API" to all node_modules.
It is safe to enable this loader for all the files. But place it after babel-loader, if babel-loader is present.
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
include: /node_modules/,
use: ['react-hot-loader/webpack'],
},
],
},
}
Webpack plugin will also land a "hot" patch to react-dom, making React-Hot-Loader more compliant to the principles.
React-🔥-Dom
Another way to make RHL more compliant is to use our version of React-Dom - hot-loader/react-dom
It is the same React-Dom, with the same version, just with our patches already landed inside.
There is 2 ways to install it:
- Use yarn name resolution, so
@hot-loader/react-dom
would be installed instead of react-dom
yarn add react-dom@npm:@hot-loader/react-dom
...
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom'
}
}
...
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:
import { asyncComponent } from 'react-async-component'
const AsyncHello = asyncComponent({
resolve: () => import('./Hello'),
})
export default AsyncHello
import { hot } from 'react-hot-loader'
const Hello = () => 'Hello'
export default hot(module)(Hello)
Checking Element type
s
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)
React Hot Loader exposes a function areComponentsEqual
to make it possible:
import { areComponentsEqual } from 'react-hot-loader'
const element = <Component />
areComponentsEqual(element.type, Component)
Another way - compare "rendered" element type
const element = <Component />
console.log(element.type === <Component />.type)
const element = <Component />
const ComponentType = <Component />.type
console.log(element.type === ComponentType)
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')
This is something we did not solve yet. Cold API could help keep original types.
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)
<SomeComponent />.type === SomeComponent
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),
onComponentCreate: (type, name) => name.indexOf('styled') > 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.
Consider using webpack-loader for this.
React-Hooks
React hooks are not really supported by React-Hot-Loader. Mostly due to our internal
processes of re-rendering React Tree, which is required to reconcile an updated application
before React will try to rerender it, and fail to do that, obviously.
- hooks should work for versions 4.6.0 and above (
pureSFC
is enabled by default). - hooks will produce errors on every hot-update without patches to
react-dom
. - hooks may loss the state without patches to
react-dom
. - hooks does not support adding new hooks on the fly
- change in hooks for a mounted components will cause a runtime exception, and a
retry
button (at the nearest class component) will be shown.
Pressing a retry
button will basically remount tree branch.
To mitigate any hook-related issues (and disable their hot-reloadability) - cold
them.
- cold components using hooks.
import { setConfig, cold } from 'react-hot-loader'
setConfig({
onComponentCreate: (type, name) =>
(String(type).indexOf('useState') > 0 ||
String(type).indexOf('useEffect') > 0) &&
cold(type),
})
API
hot(Component, options)
Mark a component as hot.
Babel plugin
Right now babel plugin has only one option, enabled by default.
safetyNet
- will help you properly setup ReactHotLoader.
You may disable it to get more control on the module execution order.
{
"plugins": [
[
"react-hot-loader/babel",
{
"safetyNet": false
}
]
]
}
Important
!! Use hot
only for module exports
, not for module imports
. !!
import { hot } from 'react-hot-loader/root'
const App = () => 'Hello World!'
export default hot(App)
Keep in mind - by importing react-hot-loader/root
you are setting up a boundary for update event propagation.
The higher(in module hierarchy) you have it - the more stuff would be updated on Hot Module Replacement.
To make RHL more reliable and safe, please place hot
below (ie somewhere in imported modules):
- react-dom
- redux store creation
- any data, you want to preserve between updates
- big libraries
You may(but it's not required) place hot
to the every route/page/feature/lazy chunk, thus make updates more scoped.
You don't need to wrap every component with hot
, application work work fine with a single one.
(old)hot(module, options)(Component, options)
Mark a component as hot. The "new" hot is just hidding the first part - hot(module)
, giving you
only the second (App)
. The "new" hot is using old API.
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)
This low-level approach lets you make **hot **imports__, not exports.
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)
if (module.hot) {
module.hot.accept('./containers/App', () => {
render(App)
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)
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.ignoreSFC
: skip "patch" for SFC. "Hot loading" could still work, wit webpack-patch presentpureRender
: do not amend render
method of any component.- for the rest see index.d.ts.
import { setConfig } from 'react-hot-loader'
setConfig({ logLevel: 'debug' })
It is important to set configuration before any other action will take a place
import './rhlConfig'
import React from 'react'
....
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:
import React from 'react'
const App = () => <div>Hello world!</div>
export default App
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)
if (module.hot) {
module.hot.accept('./containers/App', () => {
render(App)
render(require('./containers/App'))
})
}
React Hot Loader v4:
import React from 'react'
import { hot } from 'react-hot-loader'
const App = () => <div>Hello world!</div>
export default hot(module)(App)
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 Boundary is inside every component
Since 4.5.4
On Hot Module Update we will inject componentDidCatch
and a special render
to every Class-based component you have, making Error Boundaries more local.
After update we will remove all sugar, keeping only Boundaries you've created.
You can provide your own errorReporter
, via setConfig({errorReporter})
or opt-out from
root ErrorBoundaries setting errorBoundary={false}
prop on AppContainer
or hot
.
However - this option affects only SFC behavior, and any ClassComponent would boundary itself.
import { setConfig } from 'react-hot-loader'
import ErrorBoundary from './ErrorBoundary'
setConfig({ errorReporter: ErrorBoundary })
If errorReporter
is not set - full screen error overlay would be shown.
Setting global Error Reporter
Global Error Reporter would, created a fixed overlay on top the page,
would be used to display errors, not handled by errorReporter
, and
any HMR error.
You may change, or disable this global error overlay
setConfig({ ErrorOverlay: () => null })
setConfig({ ErrorOverlay: MyErrorOverlay })
The UX of existing overlay is a subject to change, and we are open to any proposals.
Known limitations and side effects
Note 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.
Backers
Thank you to all our backers! 🙏 Become a backer
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Become a sponsor
License
MIT