React-Hot-Loader 4.6

thekashey

Anton Korzunov

Posted on December 13, 2018

React-Hot-Loader 4.6

What's inside?

  • New top-level API for better HMR
  • React 16.6 – React.lazy, React.memo and forwardRef support
  • React Hooks support
  • React-🔥-dom
  • Webpack plugin
  • Automagic ErrorBoundaries
  • Pure Render
  • "Principles"

New top level API

The new hot api is even hotter than before!

Before

import {hot} from 'react-hot-loader';
....
export default hot(module)(MyComponent)
Enter fullscreen mode Exit fullscreen mode

Now

import {hot} from 'react-hot-loader/root';
....
export default hot(MyComponent)
Enter fullscreen mode Exit fullscreen mode

Why? We just moved hot(module) inside /root, so HMR would be configured before the module execution, and we would be able to handle errors during update.

Hot reloading shouldn't stop after an error #1078

Description

Currently, if there is a compilation error in the code being hot reloaded, I will see an overlay containing information about the errors, as well as a message in the console about these errors. However, after these errors are addressed, hot reloading should continue as normal, but currently isn't.

Expected behavior

When I save a piece of code with an error, then fix the error, I should be able to continue editing and hot reloading my application. This works perfectly fine without using react-hot-loader, and just using webpack-hot-middleware.

function render(rootContainer: JSX.Element) {
    ReactDOM.render(
        <Provider store={store}>
            <ConnectedRouter history={history}>
                {rootContainer}
            </ConnectedRouter>
        </Provider>,
        document.getElementById('root')
    );
}

render(<Routes />);

//configure hot module replacement during development
if(module.hot) {
    module.hot.accept('./routes', () => {
        const NewRoutes = require('./routes').Routes;
        render(<NewRoutes />);
    });
}
Enter fullscreen mode Exit fullscreen mode

image

Actual behavior

What actually happens:

When using react-hot-loader with hot(module)( ... ) in my Routes component, my application is no longer able to hot reload after I fix the error. I see the following: image

I see the error overlay disappear, but my component does not get updated any more.

Environment

React Hot Loader version: 4.3.11

Run these commands in the project folder and fill in their results:

  1. node -v: v8.10.0
  2. npm -v: 6.4.1

Then, specify:

  1. Operating system: Windows 10
  2. Browser and version: Google Chrome Version 69.0.3497.100 (Official Build) (64-bit)

Reproducible Demo

I can do if necessary

React 16.6 support

forwardRef would just work, memo will be updated on HMR(using our new super deep force update), and lazy would auto import updated module.
Everything out of the box.

React Hooks support

After React 16.7 release we found that hooks are not supported. At all. As result hooks were not supported by all our consumers, including Storybook and Gatsby.

gatsby-develop | useState from react hooks doesn't work in development environment #9489

Description

When running dev server, the state from react hooks is not working without any errors. It works, however, in production after compiling the files.

Steps to reproduce

  1. Pull down https://github.com/aamorozov/gatsby-react-hooks;
  2. Run yarn && gatsby develop;
  3. When dev server is running, click on the button - the state is not getting updated here;
  4. Run gatsby build && gatsby serve
  5. When prod server is running, click on the button - the state is being updated correctly.

Expected result

The state from react-hooks should work in both dev/prod builds.

Actual result

The state from react-hooks is working only in production build.

Environment

System:
    OS: macOS 10.14
    CPU: x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 11.0.0 - /usr/local/bin/node
    Yarn: 1.10.1 - /usr/local/bin/yarn
    npm: 6.4.1 - /usr/local/bin/npm
  Browsers:
    Chrome: 69.0.3497.100
    Firefox: 62.0.3
    Safari: 12.0
  npmPackages:
    gatsby: ^2.0.19 => 2.0.28
    gatsby-plugin-flow: ^1.0.2 => 1.0.2
    gatsby-plugin-jsxstyle: ^0.0.3 => 0.0.3
    gatsby-plugin-manifest: ^2.0.5 => 2.0.5
    gatsby-plugin-offline: ^2.0.5 => 2.0.7
    gatsby-plugin-react-helmet: ^3.0.0 => 3.0.0
  npmGlobalPackages:
    gatsby-cli: 2.4.3

Storybook not compatible with React hooks #4691

Describe the bug Attempting to render a react component that uses hooks into a storybook staging environment throws an error Hooks can only be called inside the body of a function component.

To Reproduce Steps to reproduce the behavior:

  1. Create a react component that uses hooks
  2. Import & render the component in storybook

Expected behavior Storybook should be able to display React components that use hooks.

Code snippets

Component code

import React, { useState } from "react";

export default function ColorChanger() {
  const [color, setColor] = useState("#000");
  const randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16);
  return (
    <div style={{ color }} onClick={() => setColor(randomColor)}>
      Color is: {color} (click to change)
    </div>
  );
}

Note, this code is working on codesandbox: https://codesandbox.io/s/n5rmo77jx0

System:

  • Browser: Firefox
  • Framework: React
  • Version: 4.0.2

The problem was coming from React-Hot-Loader nature – it was wrapping SFC with Class-based component, which does not support hooks.
The community quickly found a way to fix it – just change one option, and tell RHL to stop wrapping SFC – {pureSFC: false}.

Now this option is enabled by default. And we added another, game changer option…

React-Hot-Dom

React-Hot-Loader was always about hacking into React, and hiding real updates from it by "presenting" proxy components to it, instead of the real ones. Proxy wrappers were just updating refs to the real components, they represent, thus making hot updates possible.

Version 4.5 changes this – comparison is moved to React-Dom internals, enabling better reconciliation, providing ability to stop wrapping elements(only SFC for now). So RHL could not affect type comparison as before.

To enable advances mode we need to patch react-dom code – and there are two ways to
do it – using hot-loader/react-dom(see readme)

// this would always work
yarn add @hot-loader/react-dom@npm:react-dom
// or change your webpack config
alias: {
  'react-dom': '@hot-loader/react-dom'
}
// or do the same with package.json to enable it in parcel
Enter fullscreen mode Exit fullscreen mode

or using our Webpack loader…

Webpack-loader

Webpack loader was a thing we removed in version 4. And here it again.

Use Webpack loader to:

  • To quickly handle your node_modules, and help RHL better understand your code or cold everything inside.
  • To automatically patch react-dom

Two different options to get a hotter version of React-don't would fit almost for everyone.

Automagic ErrorBoundaries

Now RHL would inform you about any Error during update, including the ones preventing the update. Also just after Hot Module Replacement (or lazy component update) RHL would inject componentDidCatch to the every Class Based component, to catch Error "just-in-place".

"Pure" Render

Is something nobody cares for years. React-Hot-Loader was replacing render by it own version like always… But React-Dev-Tools give you ability to right-click on element, and jump to source..

dev tools

https://github.com/facebook/react-devtools/pull/1191

Now you can provide option pureRender(sorry, it's not enabled by default yet), and side effect from render method would be removed.

Please test this option, and help me make it default.

PS: This option affects only "Class Componets". For SFC you have to use ignoreSFC, which possible only with react-dom patch.

Principles

And now RHL would try to be more Dan Abramov "hot-principles" compatible. We knew that modern Hot Reloading experience is far (cough) from ideal, are pretty sure that written principles are something we gonna to be compliant with.

https://overreacted.io/my-wishlist-for-hot-reloading/

🚀 track "principles" compliance #1118

https://overreacted.io/my-wishlist-for-hot-reloading/

Correctness

  • [x] (partialy) Hot reloading should be unobservable before the first edit. Until you save a file, the code should behave exactly as it would if hot reloading was disabled. It’s expected that things like fn.toString() don’t match, which is already the case with minification. But it shouldn’t break reasonable application and library logic. Hot reload shouldn’t break React rules. Components shouldn’t get their lifecycles called in an unexpected way, accidentally swap state between unrelated trees, or do other non-Reacty things.
  • [x] (partialy)Element type should always match the expected type. Some approaches wrap component types but this can break .type === MyThing. This is a common source of bugs and should not happen.
  • [x] It should be easy to support all React types. lazy, memo, forwardRef — they should all be supported and it shouldn’t be hard to add support for more. Nested variations like memo(memo(...)) should also work. We should always remount when the type shape changes. It shouldn’t reimplement a non-trivial chunk of React. It’s hard to keep up with React. If a solution reimplements React it poses problems in longer term as React adds features like Suspense.
  • [x] Re-exports shouldn’t break. If a component re-exports components from other modules (whether own or from node_modules), that shouldn’t cause issues. Static fields shouldn’t break. If you define a ProfilePage.onEnter method, you’d expect an importing module to be able to read it. Sometimes libraries rely on this so it’s important that it’s possible to read and write static properties, and for component itself to “see” the same values on itself.
  • [ ] It is better to lose local state than to behave incorrectly. If we can’t reliably patch something (for example, a class), it is better to lose its local state than to do a mixed success effort at updating it. The developer will be suspicious anyway and likely force a refresh. We should be intentional about which cases we’re confident we can handle, and discard the rest. It is better to lose local state than use an old version. This is a more specific variation of the previous principle. For example, if a class couldn’t be hot reloaded, the code should force a remount for those components with the new version rather than keep rendering a zombie.

Locality

  • [x] (partialy)Editing a module should re-execute as few modules as possible. Side effects during component module initialization are generally discouraged. But the more code you execute, the more likely something will cause a mess when called twice. We’re writing JavaScript, and React components are islands of (relative) purity but even there we don’t have strong guarantees. So if I edit a module, my hot reloading solution should re-execute that module and try to stop there if possible.
  • [x] Editing a component shouldn’t destroy the state of its parents or siblings. Similar to how setState() only affects the tree below, editing a component shouldn’t affect anything above it.
  • [x] Edits to non-React code should propagate upwards. If you edit a file with constants or pure functions that’s imported from several components, those components should update. It is acceptable to lose module state in such files.
  • [x] A runtime error introduced during hot reloading should not propagate. If you make a mistake in one component, it shouldn’t break your whole app. In React, this is usually solved by error boundaries. However, they are too coarse for the countless typos we make while editing. I should be able to make and fix runtime errors while I work on a component without its siblings or parents unmounting. However, errors that don’t happen during hot reload (and are legitimate bugs in my code) should go to the closest error boundary.
  • [ ] Preserve own state unless it’s clear the developer doesn’t want to. If you’re just tweaking styles, it’s frustrating for the state to reset on every edit. On the other hand, if you just changed the state shape or the initial state, you’ll often prefer it to reset. By default we should try our best to preserve state. But if it leads to an error during hot reload, this is often a sign some assumption has changed, so we should should reset state and retry rendering in that case. Commenting things out and back in is common so it’s important to handle that gracefully. For example, removing Hooks at the end shouldn’t reset state.
  • [ ] Discard state when it’s clear the developer wants to. In some cases we can also proactively detect that the user wants to reset. For example, if the Hook order changed, or if primitive Hooks like useState change their initial state type. We can also offer a lightweight annotation that you can use to force a component to reset on every edit. Such as // ! or some similar convention that’s fast to add and remove while you focus on how component mounts. Support updating “fixed” things. If a component is wrapped in memo(), hot reload should still update it. If an effect is called with [], it should still be replaced. Code is like an invisible variable. Previously, I thought it was important to force deep updates below for things like renderRow={this.renderRow}. But in the Hooks world, we rely on closures anyway this seems unnecessary anymore. A different reference should be sufficient.
  • [x] Support multiple components in one file. It is a common pattern that multiple components are defined in the same file. Even if we only keep the state for function components, we want to make sure putting them in one file doesn’t cause them to lose state. Note these can be mutually recursive.
  • [x] When possible, preserve the state of children. If you edit a component, it’s always frustrating if its children unintentionally lose state. As long as the element types of children are defined in other files, we expect their state to be preserved. If they’re in the same file, we should do our best effort.
  • [x] Support custom Hooks. For well-written custom Hooks (some cases like useInterval() can be a bit tricky), hot reloading any arguments (including functions) should work. This shouldn’t need extra work and follows from the design of Hooks. Our solution just shouldn’t get in the way.
  • [x] Support render props. This usually doesn’t pose problems but it’s worth verifying they work and get updated as expected.
  • [x] Support higher-order components. Wrapping export into a higher-order component like connect shouldn’t break hot reloading or state preservation. If you use a component created from a HOC in JSX (such as styled), and that component is a class, it’s expected that it loses state when instantiated in the edited file. But A HOC that returns a function component (potentially using Hooks) shouldn’t shouldn’t lose state even if it’s defined in the same file. In fact, even edits to its arguments (e.g. mapStateToProps) should be reflected.

Feedback

  • [x] Both success and failure should provide visual feedback. You should always be confident whether a hot reload succeeded or failed. In case of a runtime or a syntax error you should see an overlay which should be automatically be dismissed after it is irrelevant. When hot reload is successful, there should be some visual feedback such as flashing updated components or a notification.
  • [x] A syntax error shouldn’t cause a runtime error or a refresh. When you edit the code and you have a syntax error, it should be shown in a modal overlay (ideally, with a click-through to the editor). If you make another syntax error, the existing overlay is updated. Hot reloading is only attempted after you fix your syntax errors. Syntax error shouldn’t make you lose the state. A syntax error after reload should still be visible. If you see a modal syntax error overlay and refresh, you should still be seeing it. It categorically should not let you run the last successful version (I’ve seen that in some setups).
  • [ ] Consider exposing power user tools. With hot reloading, code itself can be your “terminal”. In addition to the hypothetical // ! command to force remount, there could be e.g. an // inspect command that shows a panel with props values next to the component. Be creative!
  • [x] (partialy)Minimize the noise. DevTools and warning messages shouldn’t expose that we’re doing something special. Avoid breaking displayNames or adding useless wrappers to the debug output.
  • [x] Debugging in major browsers should show the most recent code. While this doesn’t exactly depend on us, we should do our best to ensure the browser debugger shows the most recent version of any file and that breakpoints work as expected.
  • [ ] Optimize for fast iteration, not long refactoring. This is JavaScript, not Elm. Any long-running series of edits likely won’t hot reload well due to a bunch of mistakes that need to be fixed one by one. When in doubt, optimize for the use case of tweaking a few components in a tight iteration loop rather than for a big refactor. And be predictable. Keep in mind that if you lose developer’s trust they’ll refresh anyway.

Right now we are compliant with 14, and partially compliant with 4 more of of 22 total. 17/22 - pretty amazing! And it's known what to do next!

What's next?

  • update your hot loader!
  • replace hot with hot, now you know it means. (optional*) pick a way to land a patch to react-dom, everything is our readme… *)Everything should work even without this step, but with this step it would work better. There is just a few synthetic cases which truly require this.
  • probably no configuration needed - only new hot and new react-dom somehow wired inside.
  • And then - party hard!

⚛️🔥🤓

https://github.com/gaearon/react-hot-loader

💖 💪 🙅 🚩
thekashey
Anton Korzunov

Posted on December 13, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

React-Hot-Loader 4.6
javascript React-Hot-Loader 4.6

December 13, 2018