gaearon/react-hot-loader

[React Hooks] Incompatibility with react-hot-loader

bkniffler opened this issue Β· 76 comments

Description

The new react hooks API does not work with react-hot-loader. Using the example in my app with export default hot(module)(App); at the bottom of app.js, the following error occurs:

Expected behavior

Should work.

Actual behavior

Uncaught Error: Hooks can only be called inside the body of a function component.

Reproducible Demo

Dunno of a demo is needed, just ping me if you need one.

Partially related
facebook/react#13972

simmo commented

Yes I've just seen the same. I tried removing my hot(module)(App) equivalent but also required removing babel plugin to get hooks to work.

Yes. RHL is 100% not compatible with hooks. There is just a few reasons behind it:

  • SFC are being converted to Class components. There is reason - to be able to forceUpdate on HMR, as long there is no "update" method on SFC. I am looking for other way of forcing the update (like this. So RHL is killing SFC.

  • "hotReplacementRender". RHL is trying to do React's job, and render the old and the new app, to merge them. So, obviously, that's broken now.

I am going to draft a PR, to mitigate both problems. It will work, but not today.

This could be a workaround gatsbyjs/gatsby#9489 (comment)

Next version of RHL, with 16.6 support will add memo and lazy support, and thus break this "fix".

Right now memo-ed components are invisible to RHL, as long they are not functions, but objects.

There is a more proper fix, which would work my better and ever after - cold API

You may disable RHL for any custom type.

import { cold } from 'react-hot-loader';
cold(MyComponent);

or just search for "useState/useEffect" inside component source code, and ❄️it.

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

Meanwhile, everything depends on this - reactjs/rfcs#74

I just set pureSFC and everything works fine, this can be a problem for other pieces @theKashey?

setConfig({ pureSFC: true })

Using setConfig({ pureSFC: true }) does it for me. Even though I get the error below, it still hot-reloads even the components using hooks.

index.js:1452 React-hot-loader: reconciliation failed could not dive into [ Ζ’ children(props) {
          var child = _children(item, state, i);

          return child ? child(props) : null;
        } ] while some elements are still present in the tree.

Probably pureSFC is a good option. Just

  • don't wrap SFC with hot - top component should be a component to be able to update nested tree on HMR.
  • don't define contextTypes on it (not a problem for hooks)
  • it would probably fail to reconcile updates, and will generate some error in "hot-render", and then continue from the next renderable component.

So - just don't use "hidden" components (ie not exported as a top level variables) - and that would be ok.
Also - there could be a problem with force update.

PS: While we found a way to handle hooks, React.memo and lazy is not yet supported
(#1084)

Who's gonna write the Babel plugin which auto adds this if it detects a hook import? 😘

Probably me. I already was used to introduce per-component settings to support React.memo, and the same would work for hooks.
I'll release a new version tomorrow, with memo and hooks 🀞, but, probably, without React.Lazy support.

Styled-components v4 also reported not to work. One more thing to manage.

v4.5.0(next) solves most of 16.6/16.7 issues and could handle anything if you will got your node_modules processed by our webpack-loader.
Please give a try and report back.
PS: If someone could convert webpack loader to webpack plugin - that would be just great.

v4.5.0(next) solves most of 16.6/16.7 issues and could handle anything if you will got your node_modules processed by our webpack-loader.

What is the loader doing that can't be handled from this module? I'm not using Webpack (and I'm sure that there are plenty others who also don't), so if a Webpack loader is required for these issues to be fixed, they would still be effectively unfixed for everybody else.

You know, @joepie91, I also was not quite happy of this change. As I mentioned is the storybook issue - there is no way to ask user to include a one more webpack-loader to their configuration.
It's also not doable with Parcel, Electron, CRA and so on...

So I just did a terrible thing (everyone - please blame @joepie91 ) - forked react-dom - https://github.com/hot-loader/react-dom.

Now you may skip webpack-loader stuff, just amend your configuration to use another react-dom. As far as I know - this would work only with aliases as long as you have to rewire not only your own files, but also node_modules, including react-hot-loader.

// webpack.conf
resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom',
    .....

// for parcel add `alias` field to the package.json

// not sure about electron

This is just a quick spike, currently only for dev version and React-16.7-alpha2 (build is automated, there is no problem to publish more versions, once approach would be proven)

Bnaya commented

@theKashey there's a yarn "trick" to install package as another name:
https://twitter.com/sebmck/status/873958247304232961?lang=en

Then we got all the cases (except npm) covered. I've updated react-πŸ”₯-dom, and included production version, as long @Bnaya hack could not be undone for prod builds.

the final configuration could be like:

  1. Add webpack-loader or use special version of react-dom.
  2. Set configuration
// hotConf.js. Should be imported before any other React stuff
import { setConfig } from 'react-hot-loader'
import ReactDOM from 'react-dom';

setConfig({
  // if our patch is present - ignore all SFC
  ignoreSFC: !!ReactDOM.setHotElementComparator,
  // set this flag to support SFC if patch is not landed
  pureSFC: true,
  // remove side effect on classes, to make react-dev-tools experience better(go-to-source)
  pureRender: true,
})

Everything could work even without webpack-loader/react-hot-dom, but it could be strange experience, like React.memo could be not updated. (useMemo will be not updated in any case)

In my tests everything is working. What about you?

@theKashey I am confused now :(

What is the solution to hot-loading and hooks for create-react-app ? react-app-rewired ?

I really want to avoid to use a forked react-dom if possible.

React-Hot-Loader is not quite CRA compatible. While we don't have any good solution for CRA - just setConfig({ pureSFC: true });. That's enough for hooks to work (if you are using "next" version of RHL).

If I use
{ test: /\.jsx?$/, include: /node_modules/, use: ['react-hot-loader/webpack'], },
as sad in readme I get multiply messages of save error:
_

ERROR in (webpack)/buildin/global.js
Module build failed (from ./node_modules/react-hot-loader/webpack.js):
TypeError: aSourceMapConsumer.eachMapping is not a function
at Function.fromStringWithSourceMap (/Users/chikovvas/activity/code/own/xapnew/node_modules/react-hot-loader/node_modules/source-map/lib/source-node.js:87:24)
at Object.transform$1 (/Users/chikovvas/activity/code/own/xapnew/node_modules/react-hot-loader/dist/webpack.development.js:132:59)
@ ./node_modules/html-webpack-plugin/node_modules/lodash/lodash.js 1:0-47
@ ./node_modules/html-webpack-plugin/lib/loader.js!./client/index.html

If I try @hot-loader/react-dom alias I get:

Module not found: Error: Can't resolve 'lodash.merge' in '/Users/chikovvas/activity/code/own/xapnew/node_modules/react-hot-loader/dist'

until I'll didn't remove "react-hot-loader/babel" from .babelrc

Module not found: Error: Can't resolve 'lodash.merge' in

Use 4.5.1, not 4.5.0

TypeError: aSourceMapConsumer.eachMapping is not a function

Something is wrong on mine side :(

I've got @hot-loader/react-dom installed and aliased and the config set up, but I get this when a hot load happens:


warning @ browser.js?e834:49
routerWarning @ routerWarning.js?437b:21
Router_componentWillReceiveProps @ Router.js?9ebe:113
callComponentWillReceiveProps @ index.dev.js?2e06:12678
updateClassInstance @ index.dev.js?2e06:12888
updateClassComponent @ index.dev.js?2e06:14379
beginWork @ index.dev.js?2e06:15203
performUnitOfWork @ index.dev.js?2e06:17941
workLoop @ index.dev.js?2e06:17981
renderRoot @ index.dev.js?2e06:18067
performWorkOnRoot @ index.dev.js?2e06:18958
performWork @ index.dev.js?2e06:18870
performSyncWork @ index.dev.js?2e06:18844
requestWork @ index.dev.js?2e06:18713
scheduleWork @ index.dev.js?2e06:18522
enqueueForceUpdate @ index.dev.js?2e06:12466
Component.forceUpdate @ react.development.js?72d0:390
(anonymous) @ react-hot-loader.development.js?c2cb:1942
(anonymous) @ react-hot-loader.development.js?c2cb:1941
setTimeout (async)
updateInstances @ react-hot-loader.development.js?c2cb:1935
(anonymous) @ react-hot-loader.development.js?c2cb:1957
hotSetStatus @ customer.js:248
hotApply @ customer.js:630
cb @ process-update.js?e135:66
(anonymous) @ process-update.js?e135:82
Promise.then (async)
check @ process-update.js?e135:81
module.exports @ process-update.js?e135:42
processMessage @ client.js?fec9:268
handleMessage @ client.js?fec9:136
handleMessage @ client.js?fec9:99
15:02:50.533 index.dev.js?2e06:18812 Uncaught TypeError: scheduler.unstable_shouldYield is not a function
    at shouldYieldToRenderer (index.dev.js?2e06:18812)
    at performAsyncWork (index.dev.js?2e06:18821)
    at flushFirstCallback (scheduler.development.js?3069:128)
    at flushWork (scheduler.development.js?3069:230)
    at idleTick (scheduler.development.js?3069:569)

Any hints?

@aaronjensen - scheduler package is below version 11.
Possible solutions:

  • ensure that you have react-dom@16.7.0 installed
  • check scheduler version (why it's below)
  • (me) copy dependencies from react-dom to @hot-loader/react-dom - right now they are empty, and that would not work with complicated package resolution.

@theKashey thank you, I had an extra scheduler due to react-test-renderer and two versions of react (thanks to an older package incorrectly making react a dependency). Also, if you're using enzyme, you may need to add this to your package.json (assuming yarn):

  "resolutions": {
    "react-test-renderer": "^16.7.0-alpha.2"
  }

Otherwise you could end up with two versions of react-test-renderer which could leave you with two versions of scheduler.

I no longer get the error, but hot reloading does not work. I see the modules update in the console, but React does not rerender.

To confirm:

  1. I am not using the babel plugin
  2. I am using @hot-loader/react
  3. This is my setConfig:
    import { setConfig } from 'react-hot-loader'
    import ReactDOM from 'react-dom'
    
    setConfig({
      ignoreSFC: !!ReactDOM.setHotElementComparator,
      pureSFC: true,
      pureRender: true,
    })
    
  4. ReactDOM.setHotElementComparator is defined
  5. hot(module)(...) wraps a class component

Is there anything I'm missing or wrong? I'm sorry if it's something obvious I missed...

edit

If I add the babel plugin back in, it works, but I often get a "cold component ... has been updated" error. Maybe I misunderstood this thread...

cold component ... has been updated is ok. You configured to ignore SFC, but they got updated - I just need to ignore this situation if setHotElementComparator is set. I will fix it.

right now babel(or webpack) plugin is crucial for memo, lazy and forwardRef to work properly. But it should do something even without it.

Using setConfig to enable hooks support (e.g. like the latest version of Gatsby)...

import { setConfig } from "react-hot-loader"

setConfig({
   ignoreSFC: true,
   pureRender: true,
 })

...appears to trigger some new forwardRef errors (e.g. with some react-spring components):

Warning: Function components cannot be given refs. Attempts to access this ref will fail.

Check the render method of `AnimatedComponent`.

Everything still works, though, so just checking if these errors can be safely ignored.

(See this issue for more details.)

FYI, aliasing react-dom to @hot-loader/react-dom appears to break React Router v3, at least for us.

@aaronjensen - could you shed some light? What(How?) is broken?

@ooloth - is it with 4.5.0+, our you are still on 4.3.x?

@theKashey Sorry I left that out! It's with 4.5.1, which is the version used in Gatsby 2.0.62.

natew commented

I suddenly started getting this error, though I'm having trouble isolating why after resetting a few things:

ERROR in ./src/main.ts
Module build failed (from /Users/nw/projects/motion/orbit/packages/build/node_modules/thread-loader/dist/cjs.js):
Thread Loader (Worker 0)
aSourceMapConsumer.eachMapping is not a function

    at Function.fromStringWithSourceMap (/Users/nw/projects/motion/orbit/packages/build/node_modules/source-map/lib/source-node.js:87:24)
    at Object.transform$1 (/Users/nw/projects/motion/orbit/packages/build/node_modules/react-hot-loader/dist/webpack.development.js:132:59)
 @ multi (webpack)-dev-server/client?http://localhost:3999 (webpack)/hot/dev-server.js ./src/main null[2]

If I remove the react-hot-loader/webpack loader it runs just fine. I tried forcing source-map to 0.7.x.

EDIT: I see this referenced by @strobox

I will release an update just when I will figure out what is wrong with @ooloth issue.

@aaronjensen - could you shed some light? What(How?) is broken?

@theKashey sorry, I haven't had a chance to dig into it yet. Some of the routes were not working at all. Some did, others didn't. The url would change, but the router would not render the appropriate page.

I'll try and debug a little soon

AFAICT, this is the minimum repro for the problem:

import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, Link, browserHistory } from 'react-router'

const PageA = () => (
  <div>
    Page A <Link to="/page-b">toggle</Link>
  </div>
)

const PageB = () => (
  <div>
    Page B <Link to="/page-a">toggle</Link>
  </div>
)

const Routes = () => (
  <Router history={browserHistory}>
    <Route path="/page-a" component={PageA} />
    <Route path="/page-b" component={PageB} />
  </Router>
)

ReactDOM.render(<Routes />, document.getElementById('app'))

Visit /page-a then click the toggle link. That will take you to /page-b which will render. Click toggle again and it will change the location, but not the content.

If the alias @hot-loader/react-dom alias is commented out, it works as expected and you can toggle between both back and forth.

Ouch. That explains a lot - it thinks that PageA and PageB is the same component and hot_reload one into another.

4.5.2 released. All reported issues solved.

@theKashey thank you. I was able to get it to work, though I had to replace react-loadable with React.lazy as I had the same problem w/ components created via react-loadable.

For anyone who is in a similar situation, it doesn't seem like React.lazy works w/ react-router 3, so I did this:

import * as React from 'react'

// React router v3 does not appear to support React.lazy components directly, so
// we wrap them in another component.
const load = (loader: () => Promise<any>) => {
  const Component = React.lazy(loader)
  return (props: any) => <Component {...props} />
}

export default load

"The same problem" - one component rendered instead on another? That's critical, and I thought I fixed it... for any component, declared as a top level variable with babel or webpack plugins enabled. Without "variable registration" RHL could merge two classes with the same name, and looking the same.
Look like LoadableComponent, created by one Loadable got merged with another LoadableComponent, created by another. This is how RHL designed to work. I was counting on tree reconciliation on Loadable, not sure why it lead to obsolete fiber element comparison.

"The same problem" - one component rendered instead on another?

Yes, that's correct afaict. I haven't tried to create a minimum repro, but I'd guess separating page a and b into separate modules and using react-loadable above would do so.

This was what my load function looked like:

import * as React from 'react'
import IconLoading from 'components/icons/loading'
import Loadable from 'react-loadable'

const load = (loader: () => Promise<any>) =>
  Loadable({
    loader,
    // eslint-disable-next-line react/prop-types
    loading: ({ pastDelay }) => (pastDelay ? <IconLoading /> : null),
    delay: 300,
  })

export default load

Then I'd do something like:

const PageA = load(() => import('./pagea.js'))

If that's not enough, please let me know and I'll see if I can come up w/ a repro.

Thanks!

So the problem is very simple, and we already fought with it before - the "hot" stuff inside react-dom is always active, always ready to hot-update something, even if there is nothing to. 2 loadable components are very similar in real, and should replace each other.
I will whitelist this logic for a nextTick after the real HMR event, hope it will solve these false-positives.

v4.5.3(or just next) released. False positive merges got fixed, among a set of other problems.
🀞 release candidate.

natew commented

I'm getting the sourcemap errors again with 4.5.3 (wasn't seeing them with 4.5.2):

ERROR in ./src/main.ts
Module build failed (from /Users/nw/projects/motion/orbit/packages/build/node_modules/thread-loader/dist/cjs.js):
Thread Loader (Worker 0)
.inputSourceMap must be a boolean, object, or undefined

    at assertInputSourceMap (/Users/nw/projects/motion/orbit/packages/build/node_modules/@babel/core/lib/config/validation/option-assertions.js:110:11)
    at Object.keys.forEach.key (/Users/nw/projects/motion/orbit/packages/build/node_modules/@babel/core/lib/config/validation/options.js:107:5)
    at Array.forEach (<anonymous>)
    at validateNested (/Users/nw/projects/motion/orbit/packages/build/node_modules/@babel/core/lib/config/validation/options.js:83:21)
    at validate (/Users/nw/projects/motion/orbit/packages/build/node_modules/@babel/core/lib/config/validation/options.js:74:10)
    at loadPrivatePartialConfig (/Users/nw/projects/motion/orbit/packages/build/node_modules/@babel/core/lib/config/partial.js:66:50)
    at Object.loadPartialConfig (/Users/nw/projects/motion/orbit/packages/build/node_modules/@babel/core/lib/config/partial.js:110:18)
    at Object.<anonymous> (/Users/nw/projects/motion/orbit/packages/build/node_modules/babel-loader/lib/index.js:140:26)
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (/Users/nw/projects/motion/orbit/packages/build/node_modules/babel-loader/lib/index.js:3:103)
 @ multi (webpack)-dev-server/client?http://localhost:3999 (webpack)/hot/dev-server.js ./src/main null[2]

Edit: What in the world.... I now downgraded to 4.5.2 and still get this error but was just fine until I upgraded to 4.5.3 this morning.... checking caches

natew commented

Managed to guess a patch that seems to work for me with #1117

πŸ€·β€β™‚οΈ

natew commented

4.6.0 just went out. No longer beta.
Hooks should work out of the box without any setup needed

@theKashey You rock.

natew commented

@theKashey the docs for hooks in the readme still seem old?

I'm test driving the new version. Seems to work ok for the most part.

In one place I'm getting:

react-hot-loader.development.js:1534 Uncaught TypeError: oldType[UNWRAP_PROXY] is not a function
    at compareComponents (react-hot-loader.development.js:1534)
    at hotComponentCompare (react-hot-loader.development.js:1558)
    at reconcileSingleElement (react-dom.development.js:13743)
    at reconcileChildFibers (react-dom.development.js:13825)
    at reconcileChildren (react-dom.development.js:14200)
    at updateFunctionComponent (react-dom.development.js:14341)
    at beginWork (react-dom.development.js:15196)
    at performUnitOfWork (react-dom.development.js:17941)
    at workLoop (react-dom.development.js:17981)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)

Why could that be? I could try to compile a reproducable example, but pretty tricky to extract from the code.

Ok, I think it's happening specifically when I wrap my import like this:

Works:

import SomeComponent from 'pages/SomeComponent'
const App = () => (
  <SomeComponent />
)
export default hot(App)

Does not work:

const SomeComponent = ac(() => import('pages/SomeComponent'))
const App = () => (
  <SomeComponent />
)
export default hot(App)

function ac (importComponent) {
  return function Component (props) {
    const [C, setComponent] = useState()
    useEffect(() => {
      importComponent().then(setComponent)
    }, [])
    return C ? <C.default {...props} /> : null
  }
}

I guess it's a bit fancy, but I'm trying to load my pages async. But I'm getting that error from the previous comment.

If I use react-loadable instead, I don't get that error. But now, whenever I save a file, it updates but only shows an old version, from the previous save, not the last save. Very strange.

Ok, fwiw, if I lazy load components using React.lazy everything works great. Apologies for the noise on already heavy thread, should have opened a separate issue.

Having said that, would be interesting to understand why that bit of code above throws oldType[UNWRAP_PROXY] is not a function, might lead to some bugfix.

I tried a lot of setups on docz, but since 4.6.1 the hmr with hooks doesn't happening πŸ˜•
When I put webpack patch I got this error:

(node:67190) UnhandledPromiseRejectionWarning: Error: You must provide the URL of lib/mappings.wasm by calling SourceMapConsumer.initialize({ 'lib/mappings.wasm': ... }) before using SourceMapConsumer
    at readWasm (/Volumes/Projects/dev/iter/components/node_modules/react-hot-loader/node_modules/source-map/lib/read-wasm.js:8:13)
    at wasm (/Volumes/Projects/dev/iter/components/node_modules/react-hot-loader/node_modules/source-map/lib/wasm.js:25:16)
    at BasicSourceMapConsumer.then.that (/Volumes/Projects/dev/iter/components/node_modules/react-hot-loader/node_modules/source-map/lib/source-map-consumer.js:264:14)
    at <anonymous>
(node:67190) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:67190) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

And when I try to use setConfig with @hot-loader/react-dom I got this error:

So, without any config it's doesn't happen at all πŸ˜–

1 - look like source-map went frontend way due to fetch polyfill, probably. It's fixed in 0.8.0-beta.0, which I probably shall not use yet. See mozilla/source-map#349

2 - this error means that you have ignoreSFC: true, but not @hot-loader/react-dom - it should display a message when hot-patches are detected.

EDIT: Got this working, solution at bottom

NOT getting HMR to work with hooks:
screenshot_2019-01-09_15-00-56

Tried to follow the instructions in readme pretty closely, since was expecting hooks to be supported...

Here's a repo to reproduce: https://github.com/digitaltopo/webpack-4-simple-boilerplate/tree/react-alpha

Stacktrace:

Uncaught TypeError: Object(...) is not a function
    at Test (test.js:22)
    at mountIndeterminateComponent (react-dom.development.js:14812)
    at beginWork (react-dom.development.js:15317)
    at performUnitOfWork (react-dom.development.js:18151)
    at workLoop (react-dom.development.js:18191)
    at HTMLUnknownElement.callCallback (react-dom.development.js:150)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:200)
    at invokeGuardedCallback (react-dom.development.js:257)
    at replayUnitOfWork (react-dom.development.js:17438)
    at renderRoot (react-dom.development.js:18310)
    at performWorkOnRoot (react-dom.development.js:19166)
    at performWork (react-dom.development.js:19078)
    at performSyncWork (react-dom.development.js:19052)
    at requestWork (react-dom.development.js:18921)
    at scheduleWork (react-dom.development.js:18730)
    at scheduleRootUpdate (react-dom.development.js:19398)
    at updateContainerAtExpirationTime (react-dom.development.js:19426)
    at updateContainer (react-dom.development.js:19483)
    at ReactRoot.render (react-dom.development.js:19775)
    at eval (react-dom.development.js:19915)
    at unbatchedUpdates (react-dom.development.js:19281)
    at legacyRenderSubtreeIntoContainer (react-dom.development.js:19911)
    at render (react-dom.development.js:19981)
    at eval (index.js:21)
    at Module../src/index.js (main.js:1417)
    at __webpack_require__ (main.js:724)
    at fn (main.js:101)
    at eval (webpack:///multi_(:8080/webpack)-dev-server/client?:3:18)
    at Object.0 (main.js:1428)
    at __webpack_require__ (main.js:724)
    at main.js:791
    at main.js:794
Test @ test.js:22
mountIndeterminateComponent @ react-dom.development.js:14812
beginWork @ react-dom.development.js:15317
performUnitOfWork @ react-dom.development.js:18151
workLoop @ react-dom.development.js:18191
callCallback @ react-dom.development.js:150
invokeGuardedCallbackDev @ react-dom.development.js:200
invokeGuardedCallback @ react-dom.development.js:257
replayUnitOfWork @ react-dom.development.js:17438
renderRoot @ react-dom.development.js:18310
performWorkOnRoot @ react-dom.development.js:19166
performWork @ react-dom.development.js:19078
performSyncWork @ react-dom.development.js:19052
requestWork @ react-dom.development.js:18921
scheduleWork @ react-dom.development.js:18730
scheduleRootUpdate @ react-dom.development.js:19398
updateContainerAtExpirationTime @ react-dom.development.js:19426
updateContainer @ react-dom.development.js:19483
ReactRoot.render @ react-dom.development.js:19775
(anonymous) @ react-dom.development.js:19915
unbatchedUpdates @ react-dom.development.js:19281
legacyRenderSubtreeIntoContainer @ react-dom.development.js:19911
render @ react-dom.development.js:19981
(anonymous) @ index.js:21
./src/index.js @ main.js:1417
__webpack_require__ @ main.js:724
fn @ main.js:101
(anonymous) @ client:3
0 @ main.js:1428
__webpack_require__ @ main.js:724
(anonymous) @ main.js:791
(anonymous) @ main.js:794
react-dom.development.js:16014 The above error occurred in the <Test> component:
    in Test (created by App)
    in div (created by App)
    in App (created by HotExportedApp)
    in AppContainer (created by HotExportedApp)
    in HotExportedApp

React will try to recreate this component tree from scratch using the error boundary you provided, AppContainer.

Versions:

 "react": "^16.7.0-alpha.2"
 "react-hot-loader": "^4.6.3"
"@hot-loader/react-dom": "^16.7.0",
 "webpack": "^4.28.1",
  • Using react-hot-loader/babel in .babelrc
  • Resolving 'react-dom': '@hot-loader/react-dom' alias in webpack.config.js (confirmed it's loading: React-Hot-Loader: react-πŸ”₯-dom patch detected. You may use all the features. printed in console)

Here's how I've set my app up:

index.js (root entry)

import React from 'react';
import {setConfig} from 'react-hot-loader';
import {render} from 'react-dom';
import App from './core/App';

// Configure RHL
setConfig({
    //pureSFC: true
    ignoreSFC: true, // RHL will be __completely__ disabled for SFC
    pureRender: true // RHL will not change render method
});
render(<App />, document.getElementById('root'));

I've also tried without setConfig and with different options enabled/disabled, but always have the same result...

App.js:

import React from 'react';
import {hot} from 'react-hot-loader/root';

import Test from '../components/test';

const App = () => (
    <div>
        <h1>Hello React World!</h1>
        <Test />
    </div>
);

export default hot(App);

Test.js:

import React, {useState} from 'react';

const Test = () => {
    // Declare a new state variable, which we'll call "count"
    const [count, setCount] = useState(0);

    return (
        <div>
            <h2>Test useState hook Functional Component</h2>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
    );
};

export default Test;

If I remove useState everything works and it hot reloads.

Here's what the transpiled component code looks like:



var Test = function Test() {
  // Declare a new state variable, which we'll call "count"
  var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0),
      _useState2 = _slicedToArray(_useState, 2),
      count = _useState2[0],
      setCount = _useState2[1];

  return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", null, "Test useState hook Functional Component"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", null, "You clicked ", count, " times"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
    onClick: function onClick() {
      return setCount(count + 1);
    }
  }, "Click me"));
};

var _default = Test;
/* harmony default export */ __webpack_exports__["default"] = (_default);
;

Would appreciate any help!

EDIT:

Looks like I was able to get everything working using:

  • react & react-dom @ 16.8.0-alpha.0
  • react-hot-loader/babel babel plugin
  • react-hot-loader/webpack loader in webpack, instead of (without) @hot-loader/react-dom package (this only goes up to 16.7.0-alpha.x)
  • Didn't need to use any setConfig for react-hot-loader

Hot reloading should work in my demo repo for anyone interested: https://github.com/digitaltopo/webpack-4-simple-boilerplate/tree/react-alpha

"react-dom" does not support hooks. They are available in alpha-2, or 16.8

I'm having troubles when I yarn link a package that uses hooks. It works fine if it's installed normally though. I'm getting the 'classes cannot use hooks' issue

@ntucker - you have to specify pureSFC or ignoreSFC option.
Probably it's time to make one of them default...

Or at the very least add that to the instructions? They are quite confusing now with about 4 different ways to do things and it's not clear if they're referring to the same way or old ones. Maybe move ALL outdated methods to other files and just focus on the options for the 'latest way' to do things on the main readme.

React-Hooks should work out of the box if you are using version 4.6.0 or above (pureSFC is enabled by default).

from the readme. so I guess that's not true @theKashey ?

What does Having dom-patch enabled would solve any possible issue (ignoreSFC option is enabled) even mean? How is the parens and the first statement even related?

import { hot } from 'react-hot-loader/root';
import { setConfig } from 'react-hot-loader';
setConfig({ pureSFC: true });
export default hot(withRouter(App), { errorBoundary: false });

Is this how I'm supposed to do it now? I also have webpack alias for the dom thing, and using the babel plugin.

You might consider adding #1088 (comment) to the docs.

@theKashey well i have hotreloading working perfectly without yarn link, but it is still giving that error even after doing setConfig.

https://github.com/ntucker/anansi/tree/master/examples/typescript is the project. the yarn link is for rest-hooks

natew commented

Didn't see this anywhere in the comments here or other issues. I'm getting this when I change hooks in a component:

Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement

Is this a current known issue or limitation or due to an improper config?

@natew - try changing the configuration. RHL is trying to re-render updated application, and that "custom" render is not hooks compatible. It was just failing with alpha versions, but it was not a big deal. I didn't test 16.8, and something may change.

setConfig({
disableHotRenderer: true
})

I write a babel plugin for react hooks hot load, it wrap react hooks component with react component, https://github.com/yoution/babel-plugin-react-hooks-hot-load

natew commented

@yoution so this basically helps hooks HMR without the errors you see if you change them?

@yoution - so you are wrapping every hooked-component with Component, and thus letting RHL to setup error boundaries around it.

  • πŸ‘ would help to isolate error in hooks
  • πŸ‘Ž is not forwarding refs.

I am thinking about proxy-regeneration - if an error is detected inside hooked-component (could be detected during hot-renderer, if it is enabled) - react-hot-loader will fail the comparison between the old and the new component, causing remount.

"remount", and "reset" are proven fixtures :trollface:

@theKashey in my way, I use babel-plugin-react-transform other then react-hot-loader for hmr, but babel-plugin-react-transform does not support babel7, so I use metro-babel7-plugin-react-transform which is used by react-native hrm;as for forwarding refs, if you use refs in hook component, that may make no difference , if you want to use hooks from parent components, maybe I should add React.forwardRef in component which wrapping the hooks

@natew in my project , it works well, what do you mean?

React.forwardRef exists cos there is no other way to forward a ref. You will have to wrap your class with a forwardRef, to pass ref and another prop, to pass it down to the "real" component as a ref again. That would work, but does not sound good.

Any updates on this?

@h0jeZvgoxFepBQ2C:

  • Hooks are expected to work
  • RHL is in the deprecation mode
  • Please try React Fast Refresh in case of any πŸ€·β€β™‚οΈ