React Hot Loader 3.0 beta demo #61

Open
wants to merge 29 commits into
from

Projects

None yet
@gaearon
Owner
gaearon commented Apr 17, 2016 edited

A Big Update Is Coming

React Hot Loader 3 is on the horizon, and you can try it today. It fixes some long-standing issues with both React Hot Loader and React Transform, and is intended as a replacement for both.

Some nice things about it:

  • Editing functional components preserves state
  • Works great with higher order components
  • Little configuration
  • Disabled in production
  • Works with or without Babel (you can remove react-hot-loader/babel from .babelrc and instead add react-hot-loader/webpack to loaders)

The docs are not there yet, but they will be added before the final release.
For now, this commit is a good reference to upgrading your project from React Hot Loader 1 to React Hot Loader 3 alpha. Then see another commit as a reference for upgrading from React Hot Loader 3 alpha to React Hot Loader 3 beta.


With lessons learned both from RHL and RT, here is a demo of a unified approach.

This is really undocumented for now, and we might change API later, so feel free to play with it at your own risk. 😉

react-hot-loader/webpack is intended to be optional. We will provide a complementary react-hot-loader/babel that detects unexported components as well. You will be able to use either, depending on whether you already use Babel or not.


Known Issues

  • Server rendering AppContainer crashes (gaearon/react-hot-loader#283). Until fixed, a workaround is to render the app directly on the server instead.
  • Error reporting only works for initial mount, but this should be fixed when facebook/react#6020 lands.
  • RedboxReact sometimes fails to display an error message due to stacktracejs/stackframe#11.
  • Hot reloading doesn’t currently work with React.createFactory or React.DOM.* factories. It works either with JSX or with React.createElement. (gaearon/react-hot-loader#277)
@nfcampos

This looks great! Without the babel part of the loader, this won't yet work for HOC-wrapped components (because the original isn't exported), right?

@gaearon
Owner
gaearon commented Apr 17, 2016

Without the babel part of the loader, this won't yet work for HOC-wrapped components (because the original isn't exported), right?

Correct.

@nfcampos
nfcampos commented Apr 17, 2016 edited

React.createElement will be patched before any user code is ran, preventing issues with code like this https://github.com/reactjs/react-router/blob/master/modules/RouterContext.js#L29 ?

@CrocoDillon

Just to get this straight, react-hot-loader was superseded by react-transform-hmr right? Do you want to go back to react-hot-loader as favorite once v3.0.0 is done?

@gaearon
Owner
gaearon commented Apr 17, 2016

React.createElement will be patched before any user code is ran, preventing issues with code like this https://github.com/reactjs/react-router/blob/master/modules/RouterContext.js#L29?

Yes (presuming the configuration is correct). In this example I just put react-hot-loader/patch in entry as the first item.

Just to get this straight, react-hot-loader was superseded by react-transform-hmr right? Do you want to go back to react-hot-loader as favorite once v3.0.0 is done?

The existing implementations of both React Transform / React Hot Loader are going away. The new implementation will be branded as React Hot Loader 3, but will internally use a combination of their approaches. It will also not be webpack-specific. “Loader” originally meant a Webpack transform but since it’s a project for “hot reloading” I think it’s fine that we expand the scope, and react-hot-loader will contain opt-in react-hot-loader/webpack, react-hot-loader/babel, and potentially other integrations.

@gaearon gaearon referenced this pull request in tyscorp/react-transform-jspm-hmr Apr 17, 2016
Closed

Status #2

@tyscorp
tyscorp commented Apr 18, 2016

Here is an example repo with jspm/SystemJS used in place of webpack: https://github.com/tyscorp/react-hot-boilerplate/tree/jspm

The proxying stuff does not currently work as no exports have __source attached.

It seems like react-hot-loader/babel would work fine with jspm/SystemJS for those using plugin-babel.

I did experiment with creating react-hot-loader/systemjs by patching SystemJS' _export function with mixed results. I did manage to get ES6 class-based components to successfully hot reload while maintaining state. It looks like the "instantiate" hook (as opposed to "translate") might be more suited for this task but I was unable to write a plugin that even worked using it. Perhaps @guybedford could weigh in on the most appropriate way to write such a plugin?

@gaearon
Owner
gaearon commented Apr 18, 2016

I added Babel plugin in case you’d like to try it.

This was referenced Apr 18, 2016
@0x80
0x80 commented Apr 18, 2016 edited

I'm getting a _len is not defined, as described here. Any idea where that might come from?

@gaearon
Owner
gaearon commented Apr 18, 2016

@0x80 Seems like a Babel bug (https://phabricator.babeljs.io/T7298). I just pushed a workaround in 3.0.0-alpha.9, please update.

@nfcampos

Are there any special steps to get this to work with react-router? I'm getting Warning: [react-router] You cannot change <Router routes>; it will be ignored when I update a component

@gaearon
Owner
gaearon commented Apr 18, 2016

Are there any special steps to get this to work with react-router? I'm getting Warning: [react-router] You cannot change ; it will be ignored when I update a component

Eh, you’re right, I can’t get this working. Looks like it’s time for ReactTraining/react-router#2182 to gain some momentum!

@gaearon gaearon referenced this pull request in ReactTraining/react-router Apr 18, 2016
Closed

Hot swapping routes #2182

@alexisvincent
alexisvincent commented Apr 18, 2016 edited

@nfcampos @gaearon A workaround for ReactRouter is to put the code that stores an instance of your router into a leaf module in your dependency tree. Then when the modules are busted by the hmr process the router instance isn't recreated. I did this quite simply by creating a module allowing you to cache arbitrary functions across module replacements.

acrossReload.js

import {once} from 'lodash'

const acrossReloadStore = {}
export const acrossReload = (name, func) => {
    if(! (name in acrossReloadStore)) acrossReloadStore[name] = once(func)
    return acrossReloadStore[name]
}

router.js

...
export const router = acrossReload("router", () => createElement(Router, {history}, ...routes))()

I know it looks weird having the this as a function, basically just because I didn't extend the acrossReload function to accept values. But this is easily achievable.

Also, this will not replace update the router, so if you add a route or remove one you will need to refresh. However since this doesn't happen often, it's ok.

@gaearon
Owner
gaearon commented Apr 18, 2016 edited

@alexisvincent

I’m not sure it would be enough for this particular approach though. React Hot Loader specifically relies on createElement being called which I don’t think would happen here.

@nfcampos

I found a really hacky workaround. If you just want to play around with this in your project, add this patch to <Router> component:

Router.prototype.componentWillReceiveProps = function(nextProps) {
  let components = [];
  function grabComponents(element) {
    // This only works for JSX routes, adjust accordingly for plain JS config
    if (element.props && element.props.component) {
      components.push(element.props.component);
    }
    if (element.props && element.props.children) {
      React.Children.forEach(element.props.children, grabComponents);
    }
  }
  grabComponents(nextProps.routes || nextProps.children);
  components.forEach(React.createElement); // force patching
};

This works in my testing. It requires 3.0.0-alpha.11 to work.

@gaearon gaearon referenced this pull request in gaearon/redux-devtools Apr 18, 2016
@gaearon Update TodoMVC example to React Hot Loader 3 64f58b7
@alexisvincent

@gaearon I don't mean for react hot loader internals, it was meant as a workaround for @nfcampos. Also, this is what I was doing in react-transform. It's possible it will no longer work, but I don't see why not. createElement would be called the first time the router was constructed. But since the router instance doesn't change often. Calling a proxy.update(Comp) on lower components would update things imported by react router. Am I missing something?

@nfcampos

@gaearon thanks, i'll try that

@gaearon
Owner
gaearon commented Apr 18, 2016

@alexisvincent Unlike React Transform, RHL3 doesn’t wrap your components where they are defined, so just re-executing those files won’t do anything. If some files importing them call createElement with their new versions, it’ll be fine, but RR keeps a reference to old versions and won’t createElement the new route components, so the proxies never get a chance to update.

@gaearon gaearon referenced this pull request in gaearon/react-hot-loader Apr 18, 2016
Open

Get this working properly with React Router #249

3 of 5 tasks complete
@alexisvincent

Oh right. I see the issue. How are you planning to tell components to call createElement once they're been replaced?

@istarkov

I get patch.dev.js?f09d:40 Uncaught TypeError: createProxy is not a function

On this line
https://github.com/gaearon/react-hot-loader/pull/240/files#diff-8be7b0cf5cd0de13e69f70bcf3a11143R48

I use webpack-hot-middleware so my webpack entry looking like

  entry: [
    'webpack-hot-middleware/client',
    'react-hot-loader/patch',
    path.join(__dirname, '../client.js'),
  ],
@gaearon
Owner
gaearon commented Apr 18, 2016

@alexisvincent

Oh right. I see the issue. How are you planning to tell components to call createElement once they're been replaced?

Normally this just happens as part of the render process. Component tree gets forcefully re-rendered by AppContainer, and so any component that is currently rendered will update. Those that aren’t rendered right now, will use the new code the next time they are rendered. So it naturally gets eventually consistent. And for things like static properties, HMR is good enough and most of the code directly interfaces with the real class anyway.

@istarkov

I get patch.dev.js?f09d:40 Uncaught TypeError: createProxy is not a function

Any chance you have an old version of react-proxy? react-hot-loader requires 3.x, maybe something’s wrong in your node_modules. Try removing it and running npm install again?

@alexisvincent

@gaearon Ok I see, so essentially a forceUpdate might be worth building in here. At least into the client side implementation.

@alexisvincent

@gaearon Because my expectation would be immediate consistency. This is essentially why I would use HMR

@gaearon
Owner
gaearon commented Apr 18, 2016

@alexisvincent

Ok I see, so essentially a forceUpdate might be worth building in here.

It’s right there.

Because my expectation would be immediate consistency. This is essentially why I would use HMR

Sorry, I didn’t explain it clear enough. The currently mounted components are updated immediately. The “lazy” nature refers to components that are not currently mounted. Like when you edit a <Button> but there is not a single <Button> on the page. In this case, it will be updated next time it’s rendered, which is indistinguishable from the developer’s point of view from if it was immediately updated. “If a tree falls in a forest...”

@istarkov

Thank you @gaearon
Looks like I forgot to remove hmre from package.json and got two different react-proxy versions.
Even on clean install (rm -rf node_modules && npm install)

@gaearon
Owner
gaearon commented Apr 18, 2016

@istarkov Cool, let us know how it goes! I need some more real world usage to catch issues before this goes stable so I really appreciate your help.

@alexisvincent

@gaearon Oh Brilliant :D

@gaearon
Owner
gaearon commented Apr 18, 2016

@alexisvincent Yeah, it’s pretty crazy. I was convinced this wouldn’t work the first couple of times I considered it (back in October!) But counter-intuitively, it does 😄 .

@istarkov

The big problem as I use react-router-relay - hack above removes router warning, but components does not updated. Any thoughts?

hack above for relay router

let Router = require('react-router/lib/Router');
Router.prototype.componentWillReceiveProps = function (nextProps) {
  // Blabla as above
};
const { RelayRouter } = require('react-router-relay');
@gaearon
Owner
gaearon commented Apr 18, 2016 edited

Feel free to dig in 😄 . I haven’t checked why this might be the case.
See also the discussion in ReactTraining/react-router#2182.

@alexisvincent

@gaearon really impressed man, this is fantastic work. This approach definitely feels clean, if you know what I mean. As a comment on your medium post. Your style and format of writing is great. Presenting the problem, and then following your thought process. Especially valuable was your explanation of failed attempts. I think we need more of this kind of writing generally in the industry.

@gaearon
Owner
gaearon commented Apr 18, 2016

@nfcampos I added a hacky but harmless workaround for React Router in 3.0.0-alpha.12 so it should just work now. (The warning is still printed but you can ignore it.)

@nfcampos

@gaearon it works now! Thanks! I'll let you know if I run into any other issues.

@mgcrea
mgcrea commented Apr 19, 2016 edited

Anyone has made this work with webpack-hot-middleware? I've just followed the diff to migrate from the hmre implementation but module.hot.accept are never called anymore, worked fined before so the webpack side should be ok, any ideas? I've got nothing in my logs.

My current boilerplate is available here https://github.com/mgcrea/react-webpack-factory if you want to try (npm i; npm start), I usually edit the prop of the Counter component inside the Navbar one to test hot reloading.

@istarkov

@mgcrea I did, my entry in webpack was

  entry: [
    'webpack-hot-middleware/client',
    'react-hot-loader/patch',
    path.join(__dirname, '../client.js'),
  ],
@strobox
strobox commented Aug 5, 2016 edited

I'm using v.3.0.0-beta.1 with such config:

// lines skiped
    'webpack-hot-middleware/client',
     'react-hot-loader/patch',
// lines skiped

And gotten

ERROR in .//react-hot-loader/patch.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./lib/patch in C:
workspace\java\boot-react\frontend\node_modules\react-hot-loader
@ ./
/react-hot-loader/patch.js 1:17-39

Replace

module.exports = require('./lib/patch');

To

module.exports = require('./src/patch');

helped.
But it is my mistake in installation (copy downloaded source to node_modules) or what?

@brenoc brenoc added a commit to brenoc/webpack-config-utils that referenced this pull request Aug 15, 2016
@brenoc brenoc docs(examples): add an example
Add an example of a webpack config taken from a [React Hot Loader3](gaearon/react-hot-boilerplate#61)
PR as an example implementation of it.
ef80d4f
@brenoc brenoc referenced this pull request in kentcdodds/webpack-config-utils Aug 15, 2016
Merged

docs(examples): add an example #4

@brenoc brenoc added a commit to brenoc/webpack-config-utils that referenced this pull request Aug 15, 2016
@brenoc brenoc docs(examples): add an example
Add an example of a webpack config taken from a [React Hot Loader 3](gaearon/react-hot-boilerplate#61) PR as an example implementation of it.

enter the commit message for your changes. Lines starting
b3465d9
@brenoc brenoc added a commit to brenoc/webpack-config-utils that referenced this pull request Aug 15, 2016
@brenoc brenoc docs(examples): add an example
Add an example of a webpack config taken from a [React Hot Loader 3](gaearon/react-hot-boilerplate#61) PR as an example implementation of it.

enter the commit message for your changes. Lines starting
c2e2ffe
@kentcdodds kentcdodds added a commit to kentcdodds/webpack-config-utils that referenced this pull request Aug 15, 2016
@brenoc @kentcdodds brenoc + kentcdodds docs(examples): add an example (#4)
* docs(examples): add an example

Add an example of a webpack config taken from a [React Hot Loader 3](gaearon/react-hot-boilerplate#61) PR as an example implementation of it.

enter the commit message for your changes. Lines starting

* docs(readme): add brenoc as a contributor
79c5127
@simonchan2013
simonchan2013 commented Aug 19, 2016 edited

@istarkov hello, I also use webpack-hot-middleware, and if I add webpack-hot-middleware/client into the entry, the webpack will bundle source and do well, but the browser do nothing, and if I add webpack-hot-middleware/client?reload=true then the browser will reload all the page instead of updating the hot change, did you meet this problem? And totally, it doesn't work for stateless component for me

@CrocoDillon CrocoDillon referenced this pull request in CrocoDillon/universal-react-redux-boilerplate Aug 19, 2016
Closed

DOM not updating after HMR #10

@aarryy
aarryy commented Aug 24, 2016

@dferber90, thanks for the great work of the compatibility table. Just a quick question in terms of your solution to enable hot-loading components of async routes, does requiring code-split points essentially make async routes become sync routes? I am asking because in my local test, after adding those require statements the required components seem to get pre-loaded before the corresponding route is accessed.

@dferber90

@aarryy Yes it makes them synchronous, but only in development mode since the routes are only imported if process.env.NODE_ENV !== 'production'. This is fine since the workaround is only necessary to fix react-hot-loader which is not used in production anyways.

This workaround introduces a slight difference between your development and production environments, since the async chunks are evaluated immediately in development mode, but in production they are evaluated only after the route is visited.

@osenvosem

I use webpack-dev-middleware, webpack-hot-middleware and webpack 2. Can't make RHL work. It shows success messages in the browser console, but the DOM isn't updated. The same thing with webpack 1.

Does anybody have simple working example with webpack 2 + WDM + WHM + RHL 3?

@aarryy
aarryy commented Aug 27, 2016

@osenvosem #72 can what you are looking for. I used the same config except that I used browsersync instead of express due to the project requirement and that worked well for me.

@cheapsteak cheapsteak commented on an outdated diff Aug 29, 2016
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
-ReactDOM.render(<App />, document.getElementById('root'));
+const rootEl = document.getElementById('root');
+ReactDOM.render(
+ <AppContainer>
+ <App />
+ </AppContainer>,
+ rootEl
+);
+
+if (module.hot) {
+ module.hot.accept('./App', () => {
+ // If you use Webpack 2 in ES modules mode, you can
+ // use <App /> here rather than require() a <NextApp />.
+ const NextApp = require('./App').default;
@cheapsteak
cheapsteak Aug 29, 2016

Heads up for anyone using babel-plugin-add-module-exports, remove the .default. i.e.

const NextApp = require('./App');
@ryan-sherpa
ryan-sherpa commented Sep 9, 2016 edited

I cannot seem to get this working. Keep getting [HMR] Cannot apply update. Need to do a full reload! on a simple text change. Was working before with RHL 1.3

Does anything stand out with my setup?

// client.js

import 'react-hot-loader/patch'
import { AppContainer } from 'react-hot-loader'
import ReactRouterApp from './app'
...

const rootEl = document.querySelector(opts.selector)

ReactDOM.render(
  <AppContainer>
    <ReactRouterApp store={store} history={history} routes={routes} />
  </AppContainer>
  , rootEl
)

if (module.hot) {
  module.hot.accept('./app', () => {
    const NextApp = require('./app').default;

    ReactDOM.render(
      <AppContainer>
        <NextApp store={store} history={history} routes={routes} />
      </AppContainer>
      , rootEl
      )
  })
}
// app.js

import React from 'react'
import { Provider } from 'react-redux'
import { Router } from 'react-router'

export default class ReactRouterApp extends React.Component {
  render() {
    const { store, history, routes } = this.props

    return (
      <Provider store={store}>
        <Router history={history} routes={routes} />
      </Provider>
    )
  }
}
// webpack.hot.config.js

module.exports = {
  devtool: 'source-map',

  context: __dirname + '/app/assets/javascripts/react',

  entry: {
    client: './client.js',
    server: './server.js',
  },

  output: {
    path: __dirname + '/app/assets/javascripts/react/',
    filename: '[name]_bundle.js',
    publicPath: 'http://localhost:8080/assets/react/',
    // Used to expose server-side rendering function to global contenxt
    library: ['HS', '[name]'],
    libraryTarget: 'var',
  },

  resolve: {
    extensions: ['', '.js', '.jsx'],
  },

  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loaders: ['jsx-loader', 'babel-loader'],
        exclude: /node_modules/
      },
    ],
  },
}
// .babelrc

{
  "presets": ["react", "es2015", "stage-1"],
  "plugins": ["react-hot-loader/babel"]
}
// package.json

"devDependencies": {
  ...
  "react-hot-loader": "3.0.0-beta.3",
  "webpack-dev-server": "^1.14.1",
},
"dependencies": {
  ...
  "react": "^0.14.7",
  "react-dom": "^0.14.7",
  "webpack": "^1.12.13"
  ...
}
# To start server
NODE_ENV=development webpack-dev-server --config webpack.hot.config.js \
--hot --inline  --progress
@framerate

@ryan-sherpa I'm noticing that message on a fresh upgrade today but only when using stateless components:

import React from 'react';

const Main = () => (
    <div>
        <h1>Root Route</h1>
    </div>
);

export default Main;

If I convert that to a normal react class, it hot reloads fine. @gaearon is that a known issue? Or possibly indicative of a greater problem?

@ryan-sherpa

@framerate, thanks for the response. I just tested both smart and dumb components in my app, neither seem to hot-reload appropriately. I'm thinking it's my webpack config file not being setup properly, however I could be mistaken.

@framerate

@ryan-sherpa Yeah I have this setup on another project and it seems to be working flawlessly. This new one though I seem to be missing something as well. I can't seem to find a "checklist" of all the things to do thats easy to follow, but perhaps someone can direct us to one.

@peter-mouland

@framerate @ryan-sherpa I have an example app react-lego which shows how to add RHL v3 (sorry, not an upgrade path from v1)

the comparison in github will hopefully help you though: https://github.com/peter-mouland/react-lego/compare/react-hot-loader

@peter-mouland
peter-mouland commented Sep 9, 2016 edited

@osenvosem I've just push a branch to the same react-lego app mentioned above which shows webpack 2 working with RHL3.

this is the branch https://github.com/peter-mouland/react-lego/tree/webpack2-rhl

but i guess you want to see the changes made to get webpack 2 working, then RHL3?

https://github.com/peter-mouland/react-lego/compare/webpack2-rhl
This link shows you the 1 commit for webpack2, 1 commit for rhl3 then a 3rd just to get them playing together.

@aj1310 not sure if you're still interested, but this should work for you also.

hope that helps, any questions let me know

@gaearon
Owner
gaearon commented Sep 13, 2016

Seems like we shouldn't have any more dreaded "can't change routes" warnings with RR v4. And routes should hot reload too. https://react-router-website-uxmsaeusnn.now.sh/basic

@tuler
tuler commented Sep 14, 2016

@peter-mouland I tried you webpack2-rhl branch but I get a UNMET PEER DEPENDENCY webpack@2.1.0-beta.22

@peter-mouland

@tuler I don't get that - rm -rf node_modules && npm i in the correct branch and it was fine.
ps, any more questions, i'm happy to help - just post them in the react-lego repo to keep this thread as small as possible!

@kumar303 kumar303 referenced this pull request in mozilla/addons-frontend Sep 20, 2016
Open

Make redux actions hot reloadable again #1096

@fpaboim
fpaboim commented Sep 24, 2016

While trying to upgrade to v3 I'm getting (npm debug log) using with express:

41 error argv "/home/fpaboim/.nvm/versions/node/v6.3.1/bin/node" "/home/fpaboim/.nvm/versions/node/v6.3.1/bin/npm" "install" "babel-plugin-react-hot-loader/babel" "--save"
42 error node v6.3.1
43 error npm  v3.10.3
44 error code 128
45 error Command failed: git clone --template=/home/fpaboim/.npm/_git-remotes/_templates --mirror git@github.com:babel-plugin-react-hot-loader/babel.git /home/fpaboim/.npm/_git-remotes/git-github-com-babel-plugin-react-hot-loader-babel-git-6e2be987
45 error Cloning into bare repository '/home/fpaboim/.npm/_git-remotes/git-github-com-babel-plugin-react-hot-loader-babel-git-6e2be987'...
45 error ERROR: Repository not found.
45 error fatal: Could not read from remote repository.
45 error
45 error Please make sure you have the correct access rights
45 error and the repository exists.

when I insert:

...
"plugins": ["react-hot-loader/babel"],
...

Looks like it's not finding the repo? Nevertheless, hot module reloading seems to be working when I insert the plugin! If I remove it, the message goes away, but rebuilding does not update the view. On ubuntu 16.04 in virtualbox, beta6 version of package. Thanks, awesome work!

@benwiley4000

@gaearon is your April 18 suggestion intended only for hot reloading routes themselves, not their components? I took it to mean component hot reloading, but componentWillReceiveProps isn't run (for me, at least) if all I do is modify the contents of a component.

@BerndWessels BerndWessels referenced this pull request in ctrlplusb/react-universally Oct 17, 2016
Closed

redux wrapped components are not hot reloading #129

@BerndWessels

Hi guys

Can you please post a recipe here with all the things that need to be imported and configured to make the new react-hot-loader work with redux.

I read through all the posts already and there are many projects with different attempts, but nobody actually explains exactly what are the steps to make it work.

As of now I could not find a way to make this component 'hot reload' without loosing state:

function Home(props) {
  return (
    <div onClick={() => props.onStuff(4711)}>
        Change this text and see that we loose this state: {props.stuff}
      </div>
  );
}

const mapStateToProps = (state) => {
  return {
    stuff: state.home.stuff
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onStuff: (id) => {
      dispatch({type: 'STUFF', id: id});
    }
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Home);

I also tried const Home React.createClass({ and class Home extends React.Component { but nothing helped.

@patrikholcak
patrikholcak commented Oct 18, 2016 edited

@BerndWessels I quickly created a bare-minimum demo repo: https://github.com/patrikholcak/hot-loader-demo

I couldn’t get it to work with webpack-dev-server so I ended up using webpack-dev-middleware & webpack-hot-middleware together with express.

First off, you need to add react-hot-loader/patch as an entry to webpack. It needs to be run before your app’s entry point. So:

module.exports = {
  entry: [
    'react-hot-loader/patch',
    'webpack-hot-middleware/client',
    './src/main.js'
  ],
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
  …
}

If you don’t use webpack or don’t want to add it as entry-point, you can import it in your app’s entry point but it should be on the first line.

You also need to add react-hot-loader/babel as a plugin to your .babelrc (in my case package.json)

{
  "presets": [
    "react"
  ],
  "plugins": [
    "react-hot-loader/babel"
  ]
}

Then you need to render your app in the <AppContainer> component — like this:

import { AppContainer } from 'react-hot-loader';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import App from './app';
import appReducer from './reducer';

const store = createStore(appReducer);

ReactDOM.render(
  <AppContainer>
    <Provider store={store}>
      <App />
    </Provider>
  </AppContainer>,
  rootEl
);

and the last step is to tell webpack that ./app and ./reducer know how to hot-reload themselves.

if (module.hot) {
  module.hot.accept('./reducer', () => {
    // redux store has a method replaceReducer
    store.replaceReducer(appReducer);
  });

  module.hot.accept('./app', () => {
    ReactDOM.render(
      <AppContainer>
        <Provider store={store}>
          <App />
        </Provider>
      </AppContainer>,
      rootEl
    );
  });
}

// edit: if you’re using webpack 1, I believe you’ll need to re-require the module. (I’m using webpack2)

if (module.hot) {
  module.hot.accept('./reducer', () => {
    const nextStore = require('./reducer');

    store.replaceReducer(nextStore);
  });
}

The same goes for ./app

Hope it helps!

@BerndWessels

@patrikholcak Thank you so much. That is what I was looking for and I really hope that this will become a proper official documentation.

PS Where is the best place to learn more about module.hot in more detail?

@patrikholcak
patrikholcak commented Oct 18, 2016 edited

@BerndWessels well, it’s still in a beta stage, so there’s still much to iron out and keeping the documentation on par with the code might slow the process down a bit. However, I understand the frustration — I spent a good amount of time debugging it myself.

I also have a react-router@4.0.0-alpha set up with redux controlled router, if that’s something you’re interested in.

You can read more about module.hot in the webpack documentation.

@oomathias

I also have a react-router@4.0.0-alpha set up with redux controlled router, if that’s something you’re interested in.

@patrikholcak Yes please!

@rafaelbiten

I guess some people playing with webpack 2 may still not be aware of its latest documentation, since it's a different url. The new url is https://webpack.js.org/

It isn't complete yet, but I found it super helpful, specially the sections configuration and how to.

@peter-mouland

here is my set up with a webpack 2, react-router 4 and react-hot-load v3.
each one added in a new branch making it very easy to see the changes required.

hope it helps.

https://github.com/peter-mouland/react-lego#server-side-rendering-ssr

@patrikholcak

@oomathias, @rafaelbiten, @geowarin — Just pushed a new commit to patrikholcak/hot-loader-demo. I might keep updating it, but I certainly will not be able to provide any support. It’s quickly put together and the implementation is very rough. I wouldn’t recommend running it in production (although I am).

I know about the new url and the documentation is awesome, but I don’t really want to link to it as it’s not fully written and might cause confusion.

@peter-mouland Awesome repo from which I have taken some inspiration implementing this, but reading through diffs is somehow confusing to me so I wanted to give it a shot myself and go with the minimal setup

PS: I had the same idea about the lego repo a while ago, it’s so cool to see someone actually do it! :)

@CrocoDillon

All examples I've looked at are using HMR accept like the one in the official docs: How to Set Up Hot Module Replacement with React?

This part doesn't work for me (the callback is never being called so the HMR fails leaving a message in the console that a reload is required, the dependency does match otherwise it would give an error so I'm not sure why the callback isn't being called when the dependency is changed)

module.hot.accept('./components/App', render);

However self-accepting works (which also means you won't need the render function)

module.hot.accept();

Any downsides to this approach?

@patrikholcak

@CrocoDillon weird. Did you read my tutorial just above? It should work. Which webpack version are you using?

Using only if (module.hot) module.hot.accept(); should theoretically work, as it accepts the module itself. I’m not sure about the downsides though. The only one I can imagine would be using it with a module which requires some additional reload configuration (eg. redux store).

module.hot.accept('./reducer', () => {
  const nextStore = require('./reducer');

  store.replaceReducer(nextStore);
});
@Kovensky

@patrikholcak If a module self-accepts, it prevents any module that accepts it as dependency from having their accept callbacks run. If you are, for some reason, using commonjs, not running the accept callback (for the require call) also prevents the imported object from being updated.

@Kovensky

@CrocoDillon make sure the ./component/App is only imported from one place, the file that tries to accept it. A module has to be accepted by all files that, directly or indirectly, import it.

The console log should tell you why it was not accepted.

@iyn iyn referenced this pull request in erikras/redux-form Oct 27, 2016
Open

Unable to import from redux-form/immutable #1998

@jakeorr
jakeorr commented Oct 28, 2016

I'm running a webpack setup using a separately bundled DLL file similar to this: http://engineering.invisionapp.com/post/optimizing-webpack/

When I implemented React Hot Loader 3.0, hot loading was working, but would stop as soon as a component used react-redux connect. Removing react-hot-loader from the DLL and instead allowing it to be included in the main app webpack bundle solved the problem. I am now seeing correct hot loading behaviour 😃

@adailey14 adailey14 referenced this pull request in halt-hammerzeit/webpack-react-redux-isomorphic-render-example Oct 29, 2016
Closed

Would React-hot-loader 3.0 be easy to upgrade to? #14

@rshmiraf rshmiraf referenced this pull request in Remchi/reddice Nov 9, 2016
Open

Your configuration of react-hot is deprecated #5

@JeremyBernier

So after a few hours of banging my head against the wall, I was finally able to get it working with React-Router and async routes (using require.ensure). I followed @patrikholcak 's well-written instructions, but had to add a few more things.

if (module.hot) {
  module.hot.accept('./routes', () => {
    routes = require('./routes').default
    ReactDOM.render(
      <AppContainer key={Math.random()}>
        <Provider store={store}>
          <Router
            history={history}
            routes={routes}
            render={applyRouterMiddleware(useScroll())}
          />
        </Provider>
      </AppContainer>,
      document.getElementById('root')
    )
  })
}

Essentially I'm listening to the routes file, reloading it whenever there's a change, and adding a random key to AppContainer.

Please guys we need to document this somewhere. My hot reload just suddenly stopped working yesterday after clearing my node_modules, and I had to waste a lot of time on Google trying to fix this (ending up here in this random pull request). I've definitely learned the hard way that I need to shrinkwrap everything (guessing that would've prevented this).

webpack.config.js
@@ -2,7 +2,7 @@ var path = require('path');
var webpack = require('webpack');
module.exports = {
- devtool: 'eval',
+ devtool: 'cheap-module-eval-source-map',
@gaearon
gaearon Nov 20, 2016 Owner

Recommend to remove -eval here, it messes with breakpoints.

@calesce
calesce Nov 20, 2016 Collaborator

Ah I didn't know that (I'm a console-logger), have a link?

@gaearon
gaearon Nov 21, 2016 Owner

A link to what? No specific issue, just Chrome debugger getting messed up.
I recommend 'cheap-module-source-map' instead.

@calesce
calesce Nov 21, 2016 Collaborator

Looks like webpack/webpack#2145 might be the issue.

@Kovensky
Kovensky Nov 21, 2016 edited

Breakpoints (appear to) work with -eval, but only if they're not sync. Chrome only loads the source maps after it finishes running the eval call itself.

But it still mangles the stack traces 😢

@calesce calesce use cheap-module-source-map
since cheap-module-eval-source-map doesn't work with breakpoints in chrome
9609a8e
@choonkending choonkending referenced this pull request in reactGo/reactGo Nov 28, 2016
Open

Spike on react-hot-loader 3 #736

@Kikobeats

@JeremyBernier the key={Math.random() works well for me.

My index.js file looks like:

import {Router, Route, browserHistory} from 'react-router'
import { AppContainer } from 'react-hot-loader'
import ReactDOM from 'react-dom'
import React from 'react'

import App from './App'
import './style.scss'

const el = document.getElementById('app')

function render (component) {
  return ReactDOM.render(
    <AppContainer key={Math.random()}>
      <Router history={browserHistory}>
        <Route path='/' component={component} />
      </Router>
    </AppContainer>,
    el
  )
}

render(App)

if (module.hot) {
  module.hot.accept('./App', () => {
    // If you use Webpack 2 in ES modules mode, you can
    // use <App /> here rather than require() a <NextApp />.
    const NextApp = require('./App').default
    render(NextApp)
  })
}
@nkzawa nkzawa referenced this pull request in zeit/next.js Dec 3, 2016
Merged

Use react-hot-loader/webpack #338

@cescoferraro

@gaeron I got my project to work with v3 and webpack 2.2.0-rc.0 using this webpack.config.entry

entry: {
        app: [
            'webpack-hot-middleware/client',
            'react-hot-loader/patch',
            "./src/ts/index.jsx"]
    }

running the server entrypoint like node server.js. Its fast!
Is there a way I could hook this up to run with the webpack-dev-server yet?

@@ -1,3 +1,10 @@
{
- "presets": ["es2015", "stage-0", "react"]
+ "presets": [
+ ["es2015", {"modules": false}],
@tracker1
tracker1 Feb 13, 2017

s/es2015/latest/?

@Kikobeats
Kikobeats Feb 14, 2017

why is necessary disable modules? looks like works without this option

@dlebedynskyi
dlebedynskyi Feb 14, 2017

@Kikobeats disable modules has no effect on hot loader. Webpack now can handle es 6 modules and for better tree shaking this has to be off in babel.

@dlebedynskyi
dlebedynskyi Feb 14, 2017

@tracker1 might as well use babel-present-env instead

@benjamindulau

What would be the correct setup if instead of having a SPA we have an hybrid server rendered page with a bunch of standalone React components rendered independently at different places on the same page. For instance when you just want to add a JS widget (in React) on an existing page.

Note that I have a single entry point "main.js" in webpack which works with some kind of a router calling different functions (which render react components) depending on the current URL path.

For now, I have to wrap each component in a different along with module.hot.accept stuff. While this makes sense and works fine, it's kind of repetitive when I need to render more than 1 component in the same page.

I know that since each component is rendered separately, they are each different mini-application with their own lifecycle. Just wondered if I can do better than this :)

@peter-mouland

@benjamindulau i don't know if this is the right place for this discussion, but i'll answer anyway, then if any more info is needed i'd recommend stack overflow rather than adding to the discussion here.

sounds like a custom webpack-loader might work here. You can make the loader wrap the files for you. I've only ever had a hand in building one so i don't know best practises, but the reason for mine and yours were very similar. I wrapped each component in code that would render each component within a ruby app, so we could drop in components independently.

here is the source to get you started.

const loaderUtils = require('loader-utils');
const getComponentName = (componentPath) => {
  const componentPathSegments = componentPath.split('/');

  //return the second-to-last segment
  return componentPathSegments.slice(-2, -1)[0];
};

module.exports = function (source) {
  let componentName;
  let loaderSource = source;

  this.value = loaderSource;
  this.cacheable && this.cacheable();
  try {
    componentName = getComponentName(loaderUtils.getRemainingRequest(this));
  }
  catch (e) {
    return source;
  }
  try {
    loaderSource = `${source};
    window.document.addEventListener("DOMContentLoaded", function(event) {
      window.setTimeout(function(){
        const elems = document.querySelectorAll('[toga=${componentName}]');
        [].forEach.call(elems, function(elem) {
          let props;
          try {
            props = JSON.parse(elem.getAttribute('props'));
          } catch (e) {
            props = {};
          }
          const Component = (typeof exports.default === 'undefined')
              ? module.exports
              : exports.default;
          ReactDOM.render(<Component {...props}/>, elem);
        });
      }, 1);
    });`;
  }
  catch (e) {
    return source;
  }
  this.value = loaderSource;

  return loaderSource;
};
@satazor
satazor commented Feb 21, 2017

For those who are having trouble with react-router 3 + async routes + hot-module-reload, please see: gaearon/react-hot-loader#288 (comment)

You may look at https://github.com/moxystudio/react-with-moxy for a working example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment