Skip to content

Commit

Permalink
Update TodoMVC example to React Hot Loader 3
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Apr 18, 2016
1 parent 251c85c commit 64f58b7
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 8 deletions.
3 changes: 2 additions & 1 deletion examples/todomvc/.babelrc
@@ -1,3 +1,4 @@
{
"presets": ["es2015-loose", "stage-0", "react"]
"presets": ["es2015-loose", "stage-0", "react"],

This comment has been minimized.

Copy link
@LKay

LKay Sep 9, 2016

es2015-loose preset is now deprecated. You should use es2015 preset as follow according to this:

{
    "presets": [["es2015", { "loose" : true }]]
}

This comment has been minimized.

Copy link
@a-x-

a-x- May 30, 2017

{
    "presets": [["env", { "loose" : true, "browsers": "last 1 Chrome version" }]]
}
"plugins": ["react-hot-loader/babel"]

This comment has been minimized.

Copy link
@mgcrea

mgcrea Apr 19, 2016

Is there a reason not to use the env key to only load the plugin during dev (like what we used to do with the hmre preset)?

  "env": {
    "development": {
      "plugins": ["react-hot-loader/babel"]
    }
  }

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 19, 2016

Author Contributor

You can totally use it but in this case I added a process.env.NODE_ENV check into the plugin itself so it’s a no-op in prod anyway.

This comment has been minimized.

Copy link
@dennisoelkers

dennisoelkers Sep 16, 2016

But at least for me, it still gives a warning to the console that "react-hot-loader/patch" did not run immediately before the app started. Is this on purpose?

}
18 changes: 17 additions & 1 deletion examples/todomvc/index.js
@@ -1,12 +1,28 @@
import 'todomvc-app-css/index.css';
import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';

This comment has been minimized.

Copy link
@ericclemmons

ericclemmons Apr 18, 2016

Are you married to this name? This is very nondescript, vs something like <HotEnabler> or something even more accurate.

In other words, the more root wrappers an app has (<Provider>, styling, themes, etc.), the more this could warrant a clear component name.

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

I’m open to change it but I want to make it clear it’s an identity component in production and you don’t need to worry about removing it in prod builds. Also, in addition to hot reloading, it takes care of render error handling (currently on mount, and on update after facebook/react#6020 lands).

Please feel free to raise an issue at RHL repo to discuss!

This comment has been minimized.

Copy link
@oliverwoodings

oliverwoodings Apr 20, 2016

When do you think facebook/react#6020 will land? 15.x or 16.x?

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 20, 2016

Author Contributor

I think 15.1

This comment has been minimized.

Copy link
@pocketjoso

pocketjoso Jul 1, 2016

Any update? :)

This comment has been minimized.

Copy link
@HosseinAgha

HosseinAgha Dec 2, 2016

To confirm this finally merged in React 15.4.1
Redbox render errors are awesome!
Thank you @gaearon for finally merging this.

import configureStore from './store/configureStore';
import Root from './containers/Root';

const store = configureStore();

render(
<Root store={store} />,
<AppContainer

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

AppContainer just renders your root component in production. (Bundling it is not a problem.) In development, it handles hot reloading, and should handle error handling after facebook/react#6020 ships.

This comment has been minimized.

Copy link
@catamphetamine

catamphetamine Apr 18, 2016

why component? why not simply this.props.children?
@gaearon

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

Filed as gaearon/react-hot-loader#244, would you like to send a PR?

This comment has been minimized.

Copy link
@catamphetamine

catamphetamine Apr 18, 2016

@gaearon Oh, you're responsive. Like my webdesign.
I would maybe, if I could make this whole thing work in my project. Currently when I add the plugin to my .babelrc I get this strange babel-related error:

    tagSource(%variable_name%, '%variable_name%');
              ^

ReferenceError: %variable_name% is not defined

Maybe it's not a bug but rather something in my setup (server-side React rendering on Node.js).
I guess my project is too compicated and egde-cased for trying out v3-alpha.
I'll stay with the hmr plugin for now.

This comment has been minimized.

Copy link
@catamphetamine

catamphetamine Apr 18, 2016

@gaearon Oh, seems that I'm not the only one having this error:
https://gist.github.com/nfcampos/216edf34ae47dfff809df41c58b58c8f

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

Yes, it’s fixed in 3.0.0-alpha.9.

This comment has been minimized.

Copy link
@benwiley4000

benwiley4000 May 3, 2016

Worth noting for those experiencing problems (I just figured this out on my own) - no need to wrap your root component in AppContainer on the server side. If you do, it will trigger an error: transaction.checkpoint is not a function. The only reason I tried to do this was so my React checksum wouldn't be invalidated, but turns out that's not even an issue. 😃

This comment has been minimized.

Copy link
@gaearon

gaearon May 3, 2016

Author Contributor

Thanks for the info, I filed as facebook/react#6689.
Will probably need a workaround on our side in the meantime.

This comment has been minimized.

Copy link
@andrewdushane

andrewdushane Oct 11, 2016

I just upgraded from 1.3.0 to 3.0.0-beta.5. After updating my webpack config, I ran the dev server without wrapping my root component in AppContainer, and hot reloading still works. Is wrapping with AppContainer still necessary @gaearon? I was surprised it worked at all.

This comment has been minimized.

Copy link
@avindra

avindra Oct 19, 2016

@andy-j-d : I'm wondering how to get this working without AppContainer. In our app, we have multiple entry pages and each one has their own root components (several, in some cases). It would be a huge pain to wrap all of them. Wondering if there is a way to automate it, or just not have to use AppContainer at all

This comment has been minimized.

Copy link
@kimown

kimown Dec 5, 2016

@andy-j-d I have the same question, have you met some problems not wrapping AppContainer, can you share the experience here?

component={Root}
props={{ store }}
/>,
document.getElementById('root')
);

if (module.hot) {

This comment has been minimized.

Copy link
@ilyaLibin

ilyaLibin Feb 26, 2017

I have to be a better approach then having this in every single entry point.

module.hot.accept('./containers/Root', () => {

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

You have to write this thing yourself. Upside: no more dreaded Error: locals[0] does not appear to be amoduleobject errors in server, test, and other environments!

This comment has been minimized.

Copy link
@alexeyraspopov

alexeyraspopov Apr 18, 2016

Would I still be able to use old approach? locals[0] didn't disturb me a lot, since it's described by https://github.com/danmartinez101/babel-preset-react-hmre. On the other hand, I don't want to use yet another React component as a wrapper and duplicate code in entry point.

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

Would I still be able to use old approach?

You can use babel-plugin-react-transform but I don’t plan any updates to it, so you’re on your own if something breaks there.

On the other hand, I don't want to use yet another React component as a wrapper and duplicate code in entry point.

It is up to you. To me, the benefits of the new approach are that it works great with functional components, and does intrusively rewrite your code. When React lands facebook/react#6020, AppContainer will take care of error display in development too—without any need for tricky wrappers like react-transform-catch-errors which didn’t work for functional components anyway. But again, it’s your call.

This comment has been minimized.

Copy link
@atinylittleshell

atinylittleshell May 21, 2016

my Root is like this, and module.hot.accept doesn't seem to be able to figure out the dynamic dependents we pass in through props.routes. what do we do?

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

module.exports = (props) => {
  return (
    <Provider store={props.store}>
      <Router history={props.history}>
        {props.routes}
      </Router>
    </Provider>
  )
}

This comment has been minimized.

Copy link
@gaearon

gaearon May 21, 2016

Author Contributor

@kunchenguid If you declare routes outside, you’ll need to module.hot.accept them. The whole purpose of extracting <Root> is so the hot update bubbles through the import chain to it, but this won’t happen if you import routes somewhere else.

This comment has been minimized.

Copy link
@atinylittleshell

atinylittleshell May 21, 2016

@gaearon gotcha... should we wrap a AppContainer around everything we module.hot.accept though?

This comment has been minimized.

Copy link
@atinylittleshell

atinylittleshell May 21, 2016

ah i think we can just module.hot.accept the other files, and in the accept callbacks just do the same thing and render the entire Root?

This comment has been minimized.

Copy link
@keriwarr

keriwarr Sep 22, 2016

I have a component structure which looks like:
<AppContainer><Root /></AppContainer>

    <Router
      history={browserHistory}
      render={applyRouterMiddleware(useScroll())}
      onUpdate={onUpdate}
    >
      {routes}
    </Router>

where routes is a const, defined above the Router.

I get the following error: Warning: You cannot change <Router routes>; it will be ignored
I'm not sure where to go from here as this message feels somewhat contradictory to the above advice.

This comment has been minimized.

Copy link
@peter-mouland

peter-mouland Sep 22, 2016

@keriwarr I believe you have to turn routes into a function routes() rather than just a variable.

Have you also done the whole <NextApp /> thing?

see my own example app with it working react-lego in particular the react-hot-loader branch

This comment has been minimized.

Copy link
@keriwarr

keriwarr Sep 22, 2016

hm. thanks for the comment, but as far as I can tell I am now doing both of those things, and the error persists. Time to dig deeper

This comment has been minimized.

Copy link
@ardcore

ardcore Oct 6, 2016

I'm also experiencing this problem, AppContainer doesn't seem to play nicely with Router and throws Warning: You cannot change <Router routes>; it will be ignored Can anyone point me to existing example of those two behaving well together?

This comment has been minimized.

Copy link
@fracmak

fracmak Nov 21, 2016

The error goes away if you add ReactDOM.unmountComponentAtNode(document.getElementById('root')); right before you call render() again. Also fixes a memory leak this original code had

More info here: https://facebook.github.io/react/blog/2015/10/01/react-render-and-top-level-api.html

This comment has been minimized.

Copy link
@richmeij

richmeij Dec 20, 2016

@fracmak Doesn't unmounting make you lose state? I thought keeping state between reloads was one of the points of hot-reloading.

This comment has been minimized.

Copy link
@fracmak

fracmak Dec 20, 2016

good point, guess you could always try out React Router v4

This comment has been minimized.

Copy link
@Krustal

Krustal Feb 5, 2017

Could we get a migration or example implementation that uses a provider? I'm really struggling to get both react-hot-loader's AppContainer component to play nicely with the explicit Redux HMR changes. They both work in their vacuums but seems to cause problems for each other.

This comment has been minimized.

Copy link
@oshalygin

oshalygin Mar 14, 2017

@Krustal take a look at this as a solution:
reduxjs/react-redux#502

render(
<AppContainer
component={require('./containers/Root').default}

This comment has been minimized.

Copy link
@littlebee

littlebee Aug 16, 2017

the http://gaearon.github.io/react-hot-loader/getstarted/ guide, which looks like it has been updated for react-hot-loader 3, has the component as a child. Is this still an option.

props={{ store }}
/>,
document.getElementById('root')
);
});
}
8 changes: 4 additions & 4 deletions examples/todomvc/package.json
Expand Up @@ -29,8 +29,8 @@
"homepage": "https://github.com/gaearon/redux-devtools#readme",
"dependencies": {
"classnames": "^2.1.2",
"react": "^0.14.6",
"react-dom": "^0.14.6",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"react-redux": "^4.1.0",
"redux": "^3.1.1"
},
Expand All @@ -43,10 +43,10 @@
"babel-preset-stage-0": "^6.3.13",
"node-libs-browser": "^0.5.2",
"raw-loader": "^0.5.1",
"react-hot-loader": "^1.3.0",
"react-hot-loader": "^3.0.0-alpha.8",

This comment has been minimized.

Copy link
@evenchange4

evenchange4 May 5, 2016

Contributor

Should we install react-hot-loader as dependencies instead of devDependencies?

This comment has been minimized.

Copy link
@gaearon

gaearon May 5, 2016

Author Contributor

Since it’s only useful in development, I don’t think so.

This comment has been minimized.

Copy link
@benwiley4000

benwiley4000 May 5, 2016

Yes, but now AppContainer is "rendered" in the production environment. It needs react-hot-loader to know to fall through, no?

This comment has been minimized.

Copy link
@gaearon

gaearon May 5, 2016

Author Contributor

Ah, good catch. Yeah, in this case it would make sense to put it in dependencies. Pull request welcome!

This comment has been minimized.

Copy link
@evenchange4

evenchange4 May 6, 2016

Contributor

Okay, I create a PR #280.

This comment has been minimized.

Copy link
@peter-mouland

peter-mouland Oct 6, 2016

@gaearon I've kept my react-hot-loader in devDependencies and created a HMR component which either returns the AppContainer or just the children depending on the NODE_ENV. https://github.com/peter-mouland/react-lego/blob/react-hot-loader/src/app/containers/HmrContainer/HmrContainer.js

would that be the preferred solution or is that overkill just to keep the prod dependencies down?

"redux-devtools": "^3.0.1",
"redux-devtools-log-monitor": "^1.0.2",
"redux-devtools-dock-monitor": "^1.0.1",
"redux-devtools-log-monitor": "^1.0.2",
"style-loader": "^0.12.3",
"todomvc-app-css": "^2.0.1",
"webpack": "^1.9.11",
Expand Down
5 changes: 3 additions & 2 deletions examples/todomvc/webpack.config.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'react-hot-loader/patch',

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

Gotta add this one before any other code runs.

This comment has been minimized.

Copy link
@artivilla

artivilla Apr 27, 2016

If I use the CLI tool do I need all of these? As in I was planning on using --inline instead of webpack-dev-server entry points you've shown here.

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 27, 2016

Author Contributor

I think --inline will take care of the first two entries, but react-hot-loader/patch is still necessary. You can just put it as the first statement in your entry point, before any imports:

require('react-hot-loader/patch');

or

import 'react-hot-loader/patch'

It’s important that this is the first import, before any other imports.

This comment has been minimized.

Copy link
@bsa7

bsa7 Jan 24, 2017

lot of attempts, use require and import, but anyway i look only that message in chrome devtools:

React Hot Loader: It appears that "react-hot-loader/patch" did not run immediately before the app started. Make sure that it runs before any other code. For example, if you use Webpack, you can add "react-hot-loader/patch" as the very first item to the "entry" array in its config. Alternatively, you can add require("react-hot-loader/patch") as the very first line in the application code, before any other imports.

in my package json there is a script:

"start": "webpack-dashboard --inline --watch -c cyan -- node ./config/dev-server.js",

i'm insert in top of dev-server.js require hotloader as shown below:

require('react-hot-loader/patch')

var path = require('path')
var express = require('express')
var WebpackDevServer = require("webpack-dev-server")
var webpack = require('webpack')
var webpackDevMiddleware = require('webpack-dev-middleware')
...
'./index'
],
output: {
Expand All @@ -30,12 +31,12 @@ module.exports = {
module: {
loaders: [{
test: /\.js$/,
loaders: ['react-hot', 'babel'],

This comment has been minimized.

Copy link
@gaearon

gaearon Apr 18, 2016

Author Contributor

Technically you can leave react-hot-loader/webpack here if you don’t use Babel or if you use Babel but don’t use ES6 modules. If you use Babel with ES6 modules, removing it is fine.

loaders: ['babel'],

This comment has been minimized.

Copy link
@benjamine

benjamine Nov 24, 2016

looks like you can't omit the -loader suffix in webpack anymore:

Module not found: Error: Can't resolve 'babel' in '...'
BREAKING CHANGE: It's no longer allowed to omit the '-loader' prefix when using loaders.
                 You need to specify 'babel-loader' instead of 'babel'.

This comment has been minimized.

Copy link
@LKay

LKay Nov 24, 2016

You can if you add proper patter for loaders resolver. You just need to add this to your webpack config:

resolveLoader : {
    moduleExtensions: ["-loader"],
}
exclude: /node_modules/,
include: __dirname
}, {
test: /\.js$/,
loaders: ['react-hot', 'babel'],
loaders: ['babel'],
include: path.join(__dirname, '..', '..', 'src')
}, {
test: /\.css?$/,
Expand Down

8 comments on commit 64f58b7

@gaearon
Copy link
Contributor Author

@gaearon gaearon commented on 64f58b7 Apr 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the main discussion is in gaearon/react-hot-boilerplate#61.
See also temporary workaround to get this working with React Router. works now out of the box

@chibicode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to add to @gaearon's comment, it works out of the box w/ React Router after 3.0.0-alpha.12. The warning is still present but it can be ignored. More on the main thread: gaearon/react-hot-boilerplate#61 (comment)

@resistdesign
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gaearon I've got an app setup like this:

  1. 3.0.0-beta.2
  2. Babe@^5
  3. Components export ES6 modules
  4. No react-hot in the loaders
  5. I have the 3 additional dev server entries in place, before my actual entry in the array.
The issue is for some reason I can't seem to hot load and keep the internal state of a component when editing it.

The console in Chrome show's that hot loading is enabled and I see the updates happen with the module ids.

Here are the files in question:

index.js:

import ReactDOM from 'react-dom';
import React from 'react';
import App from './App';
import { AppContainer } from 'react-hot-loader';

const root = document.getElementById('root');

ReactDOM.render((
  <AppContainer
    component={App}
  />
), root);

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

    ReactDOM.render((
      <AppContainer
        component={App}
      />
    ), root);
  });
}

BaseComponent.js:

import React, { Component } from 'react';

function propsEqual(val1, val2) {
  if (val1 === val2) {
    return true;
  }

  if (
    !(val1 instanceof Object) || !(val2 instanceof Object)
  ) {
    return false;
  }

  let eq = true;

  for (let k in val1) {
    const prop1 = val1[k];

    if (val2.hasOwnProperty(k)) {
      const prop2 = val2[k];

      eq = prop1 === prop2;
    } else {
      eq = prop1 === undefined;
    }

    if (!eq) {
      return false;
    }
  }

  for (let l in val2) {
    const prop2 = val2[l];

    if (!val1.hasOwnProperty(l)) {
      eq = prop2 === undefined;
    }

    if (!eq) {
      return false;
    }
  }

  return true;
}

export default class BaseComp extends Component {
  constructor() {
    super();
  }

  shouldComponentUpdate(nextProps, nextState) {
    return (
      !propsEqual(this.props, nextProps) || !propsEqual(this.state, nextState)
    );
  }
}

App.js:

import React from 'react';
import BaseComponent from '../Base/BaseComponent';
import Input from './Input';

export default class App extends BaseComponent {
  constructor() {
    super();
  }

  state = {
    heading: 'TEST APP'
  };

  render() {
    const { heading } = this.state;

    return (
      <h1>
        {heading}
        <br />
        <Input />
      </h1>
    );
  }
}

Input.js:

import React, { PropTypes } from 'react';
import BaseComponent from '../Base/BaseComponent';

export default class Input extends BaseComponent {
  constructor() {
    super();
    this.onTextChange = ::this.onTextChange;
  }

  state = {
    value: ''
  };

  onTextChange(event) {
    const { value } = event.target;

    this.setState({ value });
  }

  render() {
    const { value } = this.state;

    return (
      <input
        style={{width: '250px'}}
        type='text'
        value={value}
        onChange={this.onTextChange}
      />
    );
  }
}

So if I enter text in the <input /> in Input and then edit the width here: style={{width: '250px'}}, when I save and the new code loads, I lose the text I entered into the input.

Am I crazy? Should this work? Does it only work with some sort of global store (flux/redux)?

Please help me out if you can :)

@resistdesign
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gaearon Ok, I've narrowed my problem to having not included the babel plugin, but when I do I get the following error:

Module build failed: TypeError: The plugin "react-hot-loader/babel" didn't export a Plugin instance at PluginManager.validate

@gperdomor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you provide an example using redux and router?... I got some errors and don't know why...

warning.js:44 Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of AppContainer.warning @ warning.js:44createElement @ ReactElementValidator.js:166patchedCreateElement @ patch.dev.js:164render @ AppContainer.dev.js:67_renderValidatedComponentWithoutOwnerOrContext @ ReactCompositeComponent.js:832_renderValidatedComponent @ ReactCompositeComponent.js:859performInitialMount @ ReactCompositeComponent.js:383performInitialMountWithErrorHandling @ ReactCompositeComponent.js:355mountComponent @ ReactCompositeComponent.js:260mountComponent @ ReactReconciler.js:47performInitialMount @ ReactCompositeComponent.js:397mountComponent @ ReactCompositeComponent.js:262mountComponent @ ReactReconciler.js:47mountComponentIntoNode @ ReactMount.js:105perform @ Transaction.js:138batchedMountComponentIntoNode @ ReactMount.js:126perform @ Transaction.js:138batchedUpdates @ ReactDefaultBatchingStrategy.js:63batchedUpdates @ ReactUpdates.js:98_renderNewRootComponent @ ReactMount.js:285_renderSubtreeIntoContainer @ ReactMount.js:371render @ ReactMount.js:392(anonymous function) @ index.js:35(anonymous function) @ index.js:241(anonymous function) @ main.js:3910__webpack_require__ @ main.js:556fn @ main.js:87(anonymous function) @ multi_main:5(anonymous function) @ main.js:586__webpack_require__ @ main.js:556(anonymous function) @ main.js:579(anonymous function) @ main.js:582
invariant.js:38 Uncaught Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of AppContainer.

@dcworldwide
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newb question. Can we use typescript instead of babel? If so could someone please suggest how to do it? Thanks

@dreyks
Copy link

@dreyks dreyks commented on 64f58b7 Aug 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the same issue as @gperdomor
I'm following the https://github.com/gaearon/redux-devtools/blob/master/examples/todomvc
Nevermind, that was just a silly typo when importing AppContainer

@resistdesign
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gaearon There should be a way that webpack can automatically wrap your entry point with an entrypoint that includes AppContainer. Not quite like the last version of reloader, but more like a real module that imports your entry point and maybe your entry point doesn't include ReactDOM render or something. Or maybe it could setup webpack to intercept ReactDOM and inject a module that includes AppContainer and ReactDOM. Just trying to think of a way to not muck up people's code with this AppContainer alien species of code! :P :)

Please sign in to comment.