React-hot-loader: React context lost on reload when loading it with useContext hook

Created on 19 Mar 2019  路  17Comments  路  Source: gaearon/react-hot-loader

After hot reload, useContext no longer returns the context, I assume the whole context is lost, haven't tried it with a normal consumer.

bug

Most helpful comment

馃檧"Oh fuck"馃檧

All 17 comments

Hooks are not quite well tested yet.
Try to disable a single feature which may break them (only if you are using hot-patch(webpack-loader or hot-loader/react-dom)

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

setConfig({disableHotRenderer: true});

My setup is as follows:

plugins: [new webpack.HotModuleReplacementPlugin()],

and

{
  test: /\.(js|jsx)$/,
  loader: 'babel-loader',
  exclude: /(node_modules)/,
  options: {
    cacheDirectory: true,
    plugins: ['react-hot-loader/babel'],
  },
},

I have tried using the disableHotRenderer and it didn't fix the problem.

Then I will ask you to create a repo to reproduce a (exactly yours) problem.

PS: And look like you are not using hot-patch

I think I was able to repro this.

I did create-react-app, ejected, added hot-loader, and added a simple context and consuming component:

https://github.com/yang/hot-cra-context-repro

Here's a video, where I add a period to App.js鈥攜ou can see from the log statement in the consuming component that it renders twice, first printing 0 (the reset value) before printing 2 (the value it should be):

https://www.youtube.com/watch?v=8b6pVEERdkg

Misc info:

  • node -v: 10.15.1
  • npm -v: 6.8.0
  • yarn -v: 1.13.0
  • Operating system: MacOS 10.14.4
  • Browser and version: Chrome 73.0.3683.103

A _new ways_ to get the context, like useContext or even dispatcher.readContext which become popular after "advice".

For now, the best solution is to disableHotRenderer (you have to have hot-patch to keep things working)

Tested it in different ways - it should just fail to hot-render and that's all, context should be still valid.

Edit: Solution to my specific issue as suggested by the next comment.

I am too _loosing the context value_ on Hot Reload (using the useContext() hook). This is my relevant code.

PS: In the index.js you also see a snippet to silence the annoying [HRM] console.logs in the browser console. Found that somewhere on github.

// index.js

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

render(App)

if (module.hot) {
    module.hot.accept('./App', () => { render(App) })
}

// This is a workaround used alongside the webpack-dev-server hot-module-reload feature
//  - it's quite chatty on the console, and there's no currently no configuration option
//    to silence it. Only used in development.
// Prevent messages starting with [HMR] or [WDS] from being printed to the console
(function(global) {
    var console_log = global.console.log
    global.console.log = function() {
        if (!(
            arguments.length == 1 &&
            typeof arguments[0] === 'string' &&
            arguments[0].match(/^\[(HMR|WDS)\]/)
        )) {
            console_log.apply(global.console,arguments)
        }
    }
})(window)
// App.js
import React, {useState, useEffect} from 'react'
import { hot } from 'react-hot-loader'

function App() {
   return(<div>Hello World</div>)
}
export default hot(module)(App)

What would happen if in your index.js you will just

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

hot(module)(App) is literally wrapping App with AppComponent and calling module.hot.accept, so you have a duplication in your code.

I ran into the same and was able to fix the problem, but it contradicts your documentation.

Details


| Package | Version |
| -------- | ------- |
| react-hot-loader | ^4.12.8 |
| @hot-loader/react-dom | ^16.8.6 |
| react | ^16.8.6 |


Config


index.tsx


require('@babel/polyfill')
const React = require('react')

const App = require('./App').default
const { render } = require('react-dom')

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

export {}


App.tsx


import { hot } from 'react-hot-loader/root'
import React from 'react'
import { Provider } from 'react-redux'
import { Router } from 'react-router-dom'
import history from './history'
import Routes from './getRoutes'
import store from './store'
import { authenticateRoute, readToken, readRegToken } from './routeActions'
import ApolloProvider from './ApolloProvider'
import PageviewTracker from './components/utils/PageviewTracker'

const App = () => (
  <ApolloProvider>
    <Provider store={store}>
      <Router history={history}>
        <PageviewTracker>
          <Routes
            auth={authenticateRoute(store)}
            readToken={readToken(store)}
            readRegToken={readRegToken(store)}
          />
        </PageviewTracker>
      </Router>
    </Provider>
  </ApolloProvider>
)

export default hot(App)

Unlike in @Mykybo's case, we ran into the lost context bug in a class component (consumer). We logged out the context value in the provider and the consumer as well. The provider's value was what we expected, but the consumer only got null. Getting null results in a crash in our application.

Solution

By removing react-hot-loader/babel from our .babelrc list, it solved the lost context bug and everything works ok in dev and prod environment as well. The documentation suggests that we should include it in the .babelrc, but as it turned out, everything works without it.

Do you happen to have some idea why this action solved our problem, @theKashey?

Removing babel plugin is not a way to solve the problem. Actually without it (or webpack plugin) context should always be lost on update.

Something is not right here.

馃檧"Oh fuck"馃檧

So #1306 would fix the problem, I would release the fix tomorrow, after another check.

@theKashey Thank you! 馃檪

4.12.9 has been released. Working with and without react-hot-dom patch. Was broken 20 days ago(4.11.0), and honestly the initial problem, and the last problem are not similar, even have roughly the same effect.

I'm getting this issue running "react-hot-loader": "^4.12.15" and using useContext that passes state from a reducer via useReducer. Each reload switches back to initialState rather than persisting it - if that's not expected behaviour I can make an example of it.

No, that's not expected, and RHL does not control this hook. As a result - the only way to switch back to the initial state - remount a component.
Please create an example.

What would happen if in your index.js you will just

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

hot(module)(App) is literally wrapping App with AppComponent and calling module.hot.accept, so you have a duplication in your code.

Yes, that was indeed the problem. I had to remove both module.hot.accept and the wrapping <AppComponent>

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tiberiumaxim picture tiberiumaxim  路  4Comments

theKashey picture theKashey  路  4Comments

jljorgenson18 picture jljorgenson18  路  3Comments

mqklin picture mqklin  路  3Comments

niba picture niba  路  4Comments