JavaScript Hot Reloading your React App

Dustin Brown
Dustin Brown
2,433 Points

Anyone else getting an error when including React hot loader in the Webpack config?

I'm getting this error when I include React hot loader in the Webpack config:

Module build failed: Error: React Hot Loader: The Webpack loader is now exported separately. If you use Babel, we recommend that you remove "react-hot-loader" from the "loaders" section of your Webpack configuration altogether, and instead add "react-hot-loader/babel" to the "plugins" section of your .babelrc file. If you prefer not to use Babel, replace "react-hot-loader" or "react-hot" with "react-hot-loader/webpack" in the "loaders" section of your Webpack configuration.

I'm attempting to implement the change it recommends. I'm more just wondering if anyone else is getting this same error.

3 Answers

Dustin Brown
Dustin Brown
2,433 Points

Okay, so removing react-hot from the loaders in the Webpack config and then adding react-hot-loader/babel to the .babelrc plugins worked. Here's what those two files now look like for me:

webpack.config.js

module.exports = {
  //...

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

.babelrc

{
  "presets": ["react", "es2015"],
  "plugins": ["react-hot-loader/babel"]
}
Nicolas Hampton
Nicolas Hampton
Treehouse Staff

I noticed that too, and made that correction. React gets modified pretty regularly, so keep an eye out for errors like that and just follow the directions they give.

Älex K
Älex K
7,809 Points

Thanks, that helped me. But beware, the key inside loaders is called "loader" not "loaders"

Jesus Mendoza
Jesus Mendoza
23,254 Points

You can also use

{ loaders: ['react-hot-loader/webpack', 'babel'], test: /\.js$/, exclude: /node_modules/ }

inside your loaders instead on the .babelrc file

Jonathan Ankiewicz
Jonathan Ankiewicz
17,428 Points
ERROR in ./components/Scoreboard.js
Module parse failed: /Users/jankiewicz/projects/webpack-build/components/Scoreboard.js Unexpected token (69:9)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (69:9)
    at Parser.pp$4.raise (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:2221:15)
    at Parser.pp.unexpected (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:603:10)
    at Parser.pp$3.parseExprAtom (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1822:12)
    at Parser.pp$3.parseExprSubscripts (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1715:21)
    at Parser.pp$3.parseMaybeUnary (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1692:19)
    at Parser.pp$3.parseExprOps (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1637:21)
    at Parser.pp$3.parseMaybeConditional (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1620:21)
    at Parser.pp$3.parseMaybeAssign (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1597:21)
    at Parser.pp$3.parseParenAndDistinguishExpression (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1861:32)
    at Parser.pp$3.parseExprAtom (/Users/jankiewicz/projects/webpack-build/node_modules/webpack/node_modules/acorn/dist/acorn.js:1796:19)
 @ ./app.jsx 11:18-52
webpack: bundle is now VALID.

Scoreboard file transfer

import React from 'react';

var PLAYERS = [
  {
    name: "Jim Hoskins",
    score: 1,
    id: 1,
  },
  {
    name: "Andrew Chalkley",
    score: 23,
    id: 2,
  },
  {
    name: "Alena Holligan",
    score: 4,
    id: 3,
  },
];

var nextId = 4;

var Stopwatch = React.createClass({
    getInitialState: function(){
      return {
        running: false,
        elapsedTime: 0,
        previousTime: 0,

      }
    },
    componentDidMount: function(){
      this.interval = setInterval(this.onTick, 100);
    },
    componentWillUnmount: function(){
      clearInterval(this.interval);
    },
    onTick:function(){
      if(this.state.running){
        var now = Date.now();
        this.setState({
          previousTime: now,
          elapsedTime: this.state.elapsedTime += (now -= this.state.previousTime),
        });
      }
    },
    onStart: function(){
      this.setState({
        running:true,
        previousTime: Date.now(),
      });
    },
    onStop: function(){
      this.setState({
          running:false,
       });
    },
    onReset: function(){
      this.setState({
          elapsedTime: 0,
          previousTime: Date.now(),
        });
    },
    render: function(){
      var seconds = Math.floor(this.state.elapsedTime / 1000);

      return(
         <div className="stopwatch">
            <h2>Stopwatch</h2>
            <div className="stopwatch-time">{seconds}</div>
            {this.state.running ?
              <button onClick={this.onStop}>Stop</button>
              :
              <button onClick={this.onStart}>Start</button>
              }
            <button onClick={this.onReset}>Reset</button>
         </div>
      )
    }
  });



var AddPlayerForm = React.createClass({
    propTypes: {
      onAdd: React.PropTypes.func.isRequired,
    },
    getInitialState: function(){
      return{
        name: "",
      };
    },
    onNameChange: function(e){
      this.setState({name: e.target.value});
    },
    onSubmit: function(e){
      e.preventDefault();
      this.props.onAdd(this.state.name);
      this.setState({name: ''});
    },
    render: function(){
      return (
        <div className="add-player-form">
          <form onSubmit={this.onSubmit} onChange={this.onNameChange}>
            <input type="text" value={this.state.name}/>
            <input type="submit" value="Add Player"/>
          </form>
        </div>
      )
    }

  });

function Stats(props){
   var totalPlayers = props.players.length;
   var totalPoints = props.players.reduce(function(total, player){
      return total + player.score;
    }, 0);
    return(
      <table className="stats">
        <tbody>
          <tr>
              <td>Players: </td>
              <td>{totalPlayers}</td>
          </tr>
           <tr>
              <td>Total Points: </td>
              <td>{totalPoints}</td>
          </tr>
        </tbody>
      </table>
    )
  }

  Stats.propTypes = {
     players: React.PropTypes.array.isRequired,
  };
function Header(props) {
  return (
    <div className="header">
       <Stats players={props.players}/>
      <h1>{props.title}</h1>
      <Stopwatch />
    </div>
  );
}

Header.propTypes = {
  title: React.PropTypes.string.isRequired,
  players: React.PropTypes.array.isRequired,
};



function Counter(props){
   return (
      <div className="counter">
            <button className="counter-action decrement" onClick={function(){props.onChange(-1);}}> - </button>
            <div className="counter-score"> {props.score} </div>
            <button className="counter-action increment" onClick={function(){props.onChange(+1);}}> + </button>
          </div>
    );
  }

  Counter.propTypes = {
    score: React.PropTypes.number.isRequired,
    onChange: React.PropTypes.func.isRequired,
  }


function Player(props) {
  return (
    <div className="player">
      <div className="player-name">
        <a className="remove-player" onClick={props.onRemove}>X</a>
        {props.name}
      </div>
      <div className="player-score">
        <Counter score={props.score} onChange={props.onScoreChange}/>
      </div>
    </div>
  );
}

Player.propTypes = {
  name: React.PropTypes.string.isRequired,
  score: React.PropTypes.number.isRequired,
  onScoreChange: React.PropTypes.func.isRequired,
  onRemove: React.PropTypes.func.isRequired,
};

 var Application = React.createClass ({
    propTypes: {
          title: React.PropTypes.string,
          initialPlayers: React.PropTypes.arrayOf(React.PropTypes.shape({
            name: React.PropTypes.string.isRequired,
            score: React.PropTypes.number.isRequired,
            id: React.PropTypes.number.isRequired,
          })).isRequired,
      },
    getDefaultProps: function(){
      return {
          title: "Scoreboard",
      }
    },
    getInitialState: function(){
      return {
        players: this.props.initialPlayers,
      };
    },
    onScoreChange: function( index, delta){
      console.log(delta + ' ' + index);
      this.state.players[index].score += delta;
      this.setState(this.state);
    },
    onPlayerAdd: function(name){
      console.log('player added' +  name);
      this.state.players.push({
          name: name,
          score: 0,
          id: nextId,
      });
      this.setState(this.state);
      nextId += 1;
     },
     onRemovePlayer: function(index){
        this.state.players.splice(index, 1);
        this.setState(this.state);

     },
    render : function(){
        return (
          <div className="scoreboard">
            <Header title={this.props.title} players={this.state.players} />

            <div className="players">
              {this.state.players.map(function(player, index) {
                return (
                    <Player
                        onScoreChange={function(delta){this.onScoreChange(index, delta)}.bind(this)}
                        onRemove={function(){this.onRemovePlayer(index)}.bind(this)}
                        name={player.name}
                        score={player.score}
                        key={player.id} />
  );
              }.bind(this))}
            </div>
            <AddPlayerForm onAdd={this.onPlayerAdd} />
          </div>
        );
    }
  });


export default Scoreboard;
Nicolas Hampton
Nicolas Hampton
Treehouse Staff

From your export statement, I can see you're using webpack to transpile this from es6 to es5, because the export default statement is in es6 format. If you're using the configuration in the best answer above, then it's the name of your file that is the problem. The regular expression in the webpack.config.js above is looking for a file name ending in .jsx, but your scoreboard is named Scoreboard.js. Change it to Scoreboard.jsx, and it should work.

i'm not getting any errors, however the react-hot-loader is detecting when my css or components change, but it is not actually reloading the app? here's my webpack configuration

module.exports = {
  entry: './app.js',
  output: {
    // path: 'build',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loaders: ['react-hot-loader', 'babel-loader']
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }
    ]
  }
};
Ricky Johnston
Ricky Johnston
27,445 Points

I was getting this same issue, but then I removed react-hot-loader from my list of loaders, but kept the --hot flag in my npm start function, and it worked a charm.