docs/oss/building-features/rails-webpacker-react-integration-options.md
Looking for a comparison of React on Rails with alternatives like Inertia.js, Hotwire, and react-rails? See Comparison with Alternatives.
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:
| Gem | Props Hydration | Server-Side-Rendering (SSR) | SSR with HMR | SSR with React-Router | SSR with Code Splitting | Node 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">
<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;
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
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:
github.com/pmmmwh/react-refresh-webpack-plugin
You can see an example commit of adding this here.
Add react refresh packages:
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
# or: npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
# or: pnpm add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
Update babel.config.js adding
plugins: [
process.env.WEBPACK_DEV_SERVER && 'react-refresh/babel',
// other plugins
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({}));
}
Add the react-hot-loader and @hot-loader/react-dom npm packages.
yarn add -D react-hot-loader @hot-loader/react-dom
# or: npm install -D react-hot-loader @hot-loader/react-dom
# or: pnpm add -D react-hot-loader @hot-loader/react-dom
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,
},
],
],
}
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);
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()
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;