Hot module replacement - Updating but not re-rendering
Asked Answered
P

3

11

I am running an express server that will act as an API for my React app that is being bundled and served by webpack-dev-server.

I am trying to get hot module replacement to work, and am almost there, when I make changes to my files, I get this in the console:

Console output of HMR

But the app is never re-rendered, unless manually refreshed. Unaware if this is relevant, but when I update my .scss files, it refreshes without manually doing so, and updates as I would expect.

Versions:

"webpack": "2.1.0-beta.22"

"webpack-dev-server": "2.1.0-beta.8"

"react-hot-loader": "3.0.0-beta.5"

I tried the latest webpack but it gave me validation errors that could not be overcome.

I am running webpack via: "webpack": "webpack-dev-server --port 4000 --env.dev", and my express server is being ran on http://localhost:3000.

Here is my webpack.config.babel.js:

const webpack = require('webpack');
const { resolve, join } = require('path');
const { getIfUtils, removeEmpty } = require('webpack-config-utils')

const getEntry = (ifDev) => {
  let entry

  if (ifDev) {
    entry = {
      app: [
        'react-hot-loader/patch',
        'webpack/hot/dev-server',
        'webpack-dev-server/client?http://localhost:4000/',
        './js/index.js'
      ],
      vendor: ['react']
    }
  } else {
    entry = {
      bundle: './js/index.js',
      vendor: ['react']
    }
  }

  return entry
}

const config = env => {
  const { ifProd, ifDev } = getIfUtils(env)

  return {
    entry: getEntry(ifDev),
    output: {
      path: resolve('./public/dist/'),
      publicPath: 'http://localhost:4000/',
      filename: '[name].bundle.js',
    },
    context: resolve(__dirname, 'assets'),
    devtool: env.prod ? 'source-map' : 'eval',
    devServer: {
      contentBase: resolve('./public/dist/'),
      headers: { 'Access-Control-Allow-Origin': '*' },
      publicPath: 'http://localhost:4000/',
      hot: true,
      noInfo: true,
      inline: true
    },
    bail: env.prod,
    module: {
      loaders: [
        { test: /\.scss$/, loaders: [ 'style', 'css', 'sass' ], exclude: /node_modules|lib/ },
        { test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'babel-loader' ] },
        { test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, loader: 'file-loader' }
      ]
    },
    resolve: {
      extensions: ['.js', '.jsx']
    },
    plugins: removeEmpty([
      ifDev(new webpack.NoErrorsPlugin()),
      ifDev(new webpack.NamedModulesPlugin()),
      ifDev(new webpack.HotModuleReplacementPlugin()),

      new webpack.DefinePlugin({
        'process.env': { NODE_ENV: JSON.stringify((env.prod) ? 'production' : 'development') }
      }),
      new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
        minChunks: Infinity,
        filename: 'vendor.bundle.js'
      }),

      ifProd(new webpack.LoaderOptionsPlugin({
        minimize: true,
        debug: false
      })),
      ifProd(new webpack.optimize.UglifyJsPlugin({
        compress: { warnings: false },
        output: { comments: false },
        sourceMap: false
      }))
    ]),
  }
}

module.exports = config

Here is my .babelrc, where I call react-hot-loader

{
  "presets": [["es2015", { modules: false }], "stage-0", "react"],
  "plugins": ["react-hot-loader/babel"],
  "env": {
    "test": {
      "plugins": ["istanbul"],
      "presets": ["es2015", "stage-0", "react"]
    }
  },
  "sourceMaps": "inline"
}
Parody answered 15/10, 2016 at 11:34 Comment(0)
R
6

With React Hot Loader v3 and the Babel transform, you want to do this in the root of your component (where you do your rendering, or where you create your Redux provider):

render(
  <AppContainer>
    <Root
      store={ store }
    />
  </AppContainer>,
  document.getElementById('root')
);

if (module.hot) {
  module.hot.accept('./containers/Root', () => {
    const RootContainer = require('./containers/Root').default;
    render(
      <AppContainer>
        <RootContainer
          store={ store }
        />
      </AppContainer>,
      document.getElementById('root')
    );
  });
}

With the new version of Hot Loader, you have to explicitly accept the hot update with module.hot.accept.

In a more complex Redux project (with routing and hot reloading reducers) you could do something like this:

/**
 * Starts the React app with the Router, and renders it to the given DOM container
 * @param  {DOMElement} container
 */
export default function app(container) {
  const store = createStore(
    combineReducers({
      ...reducers,
      routing: routerReducer,
      form: formReducer,
    }),
    compose(
      applyMiddleware(
        routerMiddleware(hashHistory),
        thunkMiddleware,
        promiseMiddleware
      ),
      process.env.NODE_ENV !== 'production' && window.devToolsExtension ? window.devToolsExtension() : (param) => param
    )
  );

  if (module.hot) {
    module.hot.accept('./reducers', () => {
      const nextReducers = require('./reducers');
      const nextRootReducer = combineReducers({
        ...nextReducers,
        routing: routerReducer,
        form: formReducer,
      });
      store.replaceReducer(nextRootReducer);
    });
  }

  const history = syncHistoryWithStore(hashHistory, store);

  render({ store, history, container });

  store.dispatch(loadEventsWhenLoggedIn());

  if (module.hot) {
    module.hot.accept('./render', () => {
      const newRender = require('./render').default;
      newRender({ store, history, container });
    });
  }
}

(and render.js)

/**
 * Starts the React app with the Router, and renders it to the given DOM container
 * @param  {DOMElement} container
 */
export default function render({ store, history, container }) {
  ReactDOM.render(
    <Provider store={store}>
      <div className='container'>
        <Routes history={history} store={store} />
      </div>
    </Provider>,
    container
  );
}

For more examples you should have a look at Dan Abramov's Redux devtools example repo, for example this file: https://github.com/gaearon/redux-devtools/blob/master/examples/todomvc/index.js

Receptor answered 20/10, 2016 at 11:15 Comment(0)
D
2

HMR works automatically for CSS because style-loader supports that out-of-the-box.

With React that's not the case. You need react-hot-loader.

Install it with npm, and change this:

{ test: /\.(js|jsx)$/, exclude: /node_modules/, loaders: [ 'babel-loader' ] },

To:

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

If you want to know more, I recommend to read "Webpack’s HMR & React-Hot-Loader — The Missing Manual".

Delineator answered 15/10, 2016 at 12:56 Comment(1)
I forgot to add my .babelrc where I am calling react-hot-loader in 3.X it encourages you to move it from your config to babel.Parody
K
0

I was having this problem, added the module.hot.accept() function and it still wasn't working.

hmr [connected]... but no hot replacement.

*deleted node_module, package-lock.json, dist/build folders...

*changed webpack.config.client.production.js --> added

    plugins: [
        new webpack.ProvidePlugin({
            process: "process/browser"
        })
    ],
    resolve:{
        alias:{
            process: "process/browser"
        }
    }

then npm install --save-dev process,

added import process from 'process' to bundle entry point (main.js for me)

...

npm install

then run it.

Ken answered 27/3, 2022 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.