React Hot Reload in Ruby On Rails

Another great advantage of using Webpacker

Alessandro Rodi
Jun 19, 2018 · 5 min read

Let’s continue on the series of articles about Webpacker and Rails.

If you read my first article about switching from Sprockets to Webpacker and the second one about Hot Module Reloading of CSS, you will love to read this, where I will introduce you, step by step, to have Hot Module Reload working on your React components.

Hot Module Reloading for Javascript

We start with a simple module that implements an hello function:

# app/webpacker/src/javascripts/hello.jsexport default function hello() {
alert('Hello World from Webpacker 1');
}

and we have this event attached to a component in our page:

# app/webpacker/packs/application.jsimport hello from '../src/javascripts/hello.js'

window
.onload = function () {
document.querySelector('#speak').addEventListener('click', hello);
};
# app/views/home/show.html.erb[...]
<button id="speak">Say something</button>
a button with the event attached

If we want to enable HMR on our hello.js module we can add the following to our pack to instruct it to listen to changes to the hello.js file:

# app/webpacker/packs/application.jsif (module.hot) {
module.hot.accept('../src/javascripts/hello.js', function () {
alert('Accepting the updated hello module!');
});
}

This will reload the module but, as you can see from the video below, the button is still attached to the old function:

it keeps printing the number 1

What we need to do, is regenerate our element and attach the function again, therefore we need to change application.js like that:

# app/webpacker/packs/application.jsif (module.hot) {
module.hot.accept('../src/javascripts/hello.js', function () {
alert('Accepting the updated hello module!');
let speaker = document.querySelector('#speak');
let newSpeaker = speaker.cloneNode(true);
newSpeaker.addEventListener('click', hello);
document.body.removeChild(speaker);
document.body.appendChild(newSpeaker);
});
}

what we are doing with the code above is to remove the old element and replace it with a clone, where we attach again the event.

And finally our HMR is working 🎉

a working example of HMR

This may seem really useless, since nobody wants to add all this code and logic, in order to have HMR working, but this mechanism can be automated completely when using a framework like React.

Hot Module Reloding with React

Thanks to the React Hot Loader we can enable this feature really easily also in our Rails + Webpacker application.

Let’s see a similar implementation of what we had above, using a React component.

# app/webpacker/src/javascripts/hello_react.jsx

import React from 'react'

export default class Hello extends React.Component {
constructor(props) {
super(props);
this.state = { text: '' };
this.handleChange = this.handleChange.bind(this);
}

speak = function() {
alert('I can speak');
};

handleChange(event) {
this.setState({text: event.target.value});
}

render() {
return (<div>
<h1>Hello World</h1>
<input type="text" value={this.state.text} onChange={this.handleChange}/>
<br/>
<br/>
<button id="speak" onClick={ this.speak }>Say something</button>
</div>)
}
}

and our application.js

# app/webpacker/packs.application.jsimport React from 'react'
import
ReactDOM from 'react-dom';
import Hello from "../src/javascripts/hello_react";
const render = Component => {
ReactDOM.render(
<Component />,
document.querySelector('#root')
);
};

document.addEventListener('DOMContentLoaded', () => {
render(Hello);
});

the application, simply renders the component when the page is loaded. The component has a button, like before, that triggers an alert when clicked.

Same example, using React

We can now enable HMR for this component in a much easier way than before and start taking advantage of it really fast.

Just add the following to application.js

if (module.hot) {
module.hot.accept('../src/javascripts/hello_react.jsx', () => {
render(require('../src/javascripts/hello_react.jsx').default);
});
}

and your component will be re-rendered and all the events reattached automatically.

As you can see from the example, the functions are updated automatically but the component state is lost.
What we need to do is:

  1. install react-hot-loader package via ./bin/yarn add react-hot-loader
  2. edit the .babelrc file in the root folder, adding "react-hot-loader/babel" to the list of plugins.
  3. Make our React Component “hot” by changing the component code to:
# app/webpacker/src/javascripts/hello.jsximport React from 'react'
import
{ hot } from 'react-hot-loader'
class Hello extends React.Component {
[...]
}

export default hot(module)(Hello);

Note that we simply export a different default.

And we are now ready to take full advantage of HMR with React!

the final result

Conclusions

Happy coding!

Alessandro Rodi

Written by

Open Source Software Engineer at Renuo AG. Located in Zürich. I do stuff. Sometimes.