This weekend my project ended up being "find a React/Rails gem that works." I administer a couple different Rails 6.0 projects with fairly vanilla configurations, so chances are that someone else is going to need to setup react_on_rails and will find this useful.

Incidentally, we were migrating from webpacker-react, which to this point I would tend to recommend over react_on_rails for the cases where one doesn't need SSR. The docs and examples on the latter are great, but it has been further from a "Just Works" experience than webpacker-react was.

linkGetting HMR to work

This was hard. As source I started with the very helpful project that shakacode set up as a proof-of-concept.

linkAdding react-refresh-webpack-plugin

As best I can tell, keeping HMR functional with React and webpacker is a hot potato-type of project. There seem to be about five different approaches to realizing HMR, and most of those approaches call themselves deprecated (including the once-default npm package to get hot reloading). The latest heir to the title nobody seems to want is react-refresh-webpack-plugin, which still labels itself as "experimental" as of November 2020. Anyway, it seems to be the going default, and you can add it via

# if you prefer npm
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
# if you prefer yarn
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
# if you prefer pnpm
pnpm add -D @pmmmwh/react-refresh-webpack-plugin react-refresh

Remember to remove react-hot-loader from your packages.json directory if it had previously been included.


The shakacode HMR tutorial project adds the following line to it's babel.config.js:

return: {
presents: [ ... ]
plugins: [
process.env.WEBPACK_DEV_SERVER && 'react-refresh/babel'

I believe the process.env.WEBPACK_DEV_SERVER ties into the many-config-file split SSR/client setup from the tutorial project. If not using that, I believe that process.env.WEBPACK_DEV_SERVER would instead be isDevelopmentEnv.

linkWebpacker config files

But that project makes a great number of changes to all of the default webpacker configuration files (one for each environment) and adds three new files on behalf of allowing a split compiling of SSR and non-SSR webpacker assets. Here is the directory in question where all of those config files reside. Since I'd like to have SSR, I emulated that project.

If you don't to add a bunch of tech debt-ish boilerplate to your config directory, and merely want to get HMR working, then adding this to config/webpack/development.js ought to do the trick:

process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const { devServer } = require('@rails/webpacker')
const environment = require('./environment')
const isWebpackDevServer = process.env.WEBPACK_DEV_SERVER
if (isWebpackDevServer) {
new ReactRefreshWebpackPlugin({
overlay: {
sockPort: devServer.port
module.exports = environment.toWebpackConfig()

As far as config/webpacker.yml, nothing in the tutorial project seems to be consequentially different from the default file provided by the Rails webpacker gem. Just need to make sure hmr: true and inline: true in the development -> dev_server section.

linkWeird aside: remove packs/application.js

In one of my projects, even though the entire contents of packs/application.js appeared to be commented out, I discovered that by including that pack tag, a second version of HMR was being imported that prevented the main HMR mechanism from functioning. I doubt others will experience this but leaving it here in case you followed all the steps above, HMR still doesn't work and you're low on ideas.

linkRegistering packs

By default, React on Rails registers a pack by calling

ReactOnRails.register({ ComponentName })

In a file that resides within app/javascripts/packs. However, the gem seems to assume that one will be able to register all of a page's packs in one fell swoop, since calling register by default wipes out previous calls to ReactOnRails.register. I'm not sure how this could ever be true in medium-to-large projects -- it certainly wasn't for us. It was easy to work around though using this suggestion from this issue. Instead of starting your pack with

import ReactOnRails from "react-on

You can instead use

window.ReactOnRails = window.ReactOnRails || require('react-on-rails').default;

The ticket talks about splitting packs up not to have overlap, but I don't have time to explore that. This suggestion has managed to Just Work.

linkLoading components after xhr

While loading components after xhr looks to "Just Work" in react-rails and webpacker-react, there's some heavy lifting involved to get it working smoothly with react_on_rails. This issue (starting to sense a trend here?) describes how best to get this working:

[todo: finish figuring out]

linkCan't find react_component

For reasons that nobody has been able to figure out in three years, some Rails projects don't get access to the gem's included react_component helper. I had this happen in one of my Rails projects. Resolved by adding the following to the end of the ReactOnRails.configure do |config| in config/initializers/react_on_rails.rb:

ActiveSupport.on_load(:action_view) do
include ReactOnRailsHelper


For a use case that one assumes would be common (getting React to work on Rails), there's still a good amount of polish work that needs to be done around the edges to get access to all the best functionality. I suppose it is partly a reflection of how rapidly the space has moved forward. At any rate, even though there was a lot of tedious Googling that went into getting a working setup for us, it was certainly appreciated how many examples shakacode's Josh has provided for tinkerers like myself to be able to reverse-engineer how to get their own projects working. Thanks, Josh! We'll become customers soon enough if this setup continues to function smoothly for us.