Getting Started
    Core Concepts
    Building Features
    API Reference
    Deployment
    Upgrading
    Migrating
    Pro
    Advanced Topics
    Misc
    Shakacode logoShakaCodeDeveloped by

    Copyright 2020 ShakaCode

    Shakapacker (Rails/Webpacker) React Integration Options

    You only need props hydration if you need SSR. However, there's no good reason to have your app make a second round trip to the Rails server to get initialization props.

    Server-Side Rendering (SSR) results in Rails rendering HTML for your React components. The main reasons to use SSR are better SEO and pages display more quickly.

    These gems provide advanced integration of React with shakacode/shakapacker:

    GemProps HydrationServer-Side-Rendering (SSR)SSR with HMRSSR with React-RouterSSR with Code SplittingNode SSR
    shakacode/react_on_rails
    react-rails
    webpacker-react

    Note, Node SSR for React on Rails requires React on Rails Pro.


    As mentioned, you don't need to use a gem to integrate Rails with React.

    If you're not concerned with view helpers to pass props or server rendering, you can do it yourself:

    <%# views/layouts/application.html.erb %>
    
    <%= content_tag :div,
      id: "hello-react",
      data: {
        message: 'Hello!',
        name: 'David'
    }.to_json do %>
    <% end %>
    // app/javascript/packs/hello_react.js
    
    const Hello = (props) => (
      <div className="react-app-wrapper">
        <img src={clockIcon} alt="clock" />
        <h5 className="hello-react">
          {props.message} {props.name}!
        </h5>
      </div>
    );
    
    // Render component with data
    document.addEventListener('DOMContentLoaded', () => {
      const node = document.getElementById('hello-react');
      const data = JSON.parse(node.getAttribute('data'));
    
      ReactDOM.render(<Hello {...data} />, node);
    });

    You may see a warning like this when building a Webpack bundle using any version of React below 18:

    Module not found: Error: Can't resolve 'react-dom/client' in ....

    It can be safely suppressed in your Webpack configuration. The following is an example of this suppression in config/webpack/commonWebpackConfig.js:

    const { webpackConfig: baseClientWebpackConfig, merge } = require('shakapacker');
    
    const commonOptions = {
      resolve: {
        extensions: ['.css', '.ts', '.tsx'],
      },
    };
    
    const ignoreWarningsConfig = {
      ignoreWarnings: [/Module not found: Error: Can't resolve 'react-dom\/client'/],
    };
    
    const commonWebpackConfig = () => merge({}, baseClientWebpackConfig, commonOptions, ignoreWarningsConfig);
    
    module.exports = commonWebpackConfig;

    HMR and React Hot Reloading

    Before turning HMR on, consider upgrading to the latest stable gems and packages: https://github.com/shakacode/shakapacker#upgrading

    Configure config/shakapacker.yml file:

    development:
      extract_css: false
      dev_server:
        hmr: true
        inline: true

    This basic configuration alone will have HMR working with the default Shakapacker setup. However, a code save will trigger a full page refresh each time you save a file.

    Webpack's HMR allows the replacement of modules for React in-place without reloading the browser. To do this, you have two options:

    1. Steps below for the github.com/pmmmwh/react-refresh-webpack-plugin.
    2. Deprecated steps below for using the github.com/gaearon/react-hot-loader.

    React Refresh Webpack Plugin

    github.com/pmmmwh/react-refresh-webpack-plugin

    You can see an example commit of adding this here.

    1. Add react refresh packages: yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh -D

    2. Update babel.config.js adding

      plugins: [
        process.env.WEBPACK_DEV_SERVER && 'react-refresh/babel',
        // other plugins
    3. Update config/webpack/development.js, only including the plugin if running the WEBPACK_DEV_SERVER

      const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
      const environment = require('./environment');
      
      const isWebpackDevServer = process.env.WEBPACK_DEV_SERVER;
      
      //plugins
      if (isWebpackDevServer) {
        environment.plugins.append('ReactRefreshWebpackPlugin', new ReactRefreshWebpackPlugin({}));
      }

    React Hot Loader (Deprecated)

    1. Add the react-hot-loader and @hot-loader/react-dom npm packages.

      yarn add --dev react-hot-loader @hot-loader/react-dom
    2. Update your babel config, babel.config.js. Add the plugin react-hot-loader/babel with the option safetyNet: false:

      {
        plugins: [
          [
            'react-hot-loader/babel',
            {
              safetyNet: false,
            },
          ],
        ],
      }
    3. Add changes like this to your entry points:

      // app/javascript/app.jsx
      
      import React from 'react';
      + import { hot } from 'react-hot-loader/root';
      
      const App = () => <SomeComponent(s) />
      
      - export default App;
      + export default hot(App);
    4. Adjust your Webpack configuration for development so that sourceMapContents option for the SASS loader is false:

      // config/webpack/development.js
      
      process.env.NODE_ENV = process.env.NODE_ENV || 'development'
      
      const environment = require('./environment')
      
      // allows for editing sass/scss files directly in browser
      + if (!module.hot) {
      +   environment.loaders.get('sass').use.find(item => item.loader === 'sass-loader').options.sourceMapContents = false
      + }
      +
      module.exports = environment.toWebpackConfig()
    5. Adjust your config/webpack/environment.js:

      // config/webpack/environment.js
      
      // ...
      
      // Fixes: React-Hot-Loader: react-🔥-dom patch is not detected. React 16.6+ features may not work.
      // https://github.com/gaearon/react-hot-loader/issues/1227#issuecomment-482139583
      + environment.config.merge({ resolve: { alias: { 'react-dom': '@hot-loader/react-dom' } } });
      
      module.exports = environment;