ShakaCode offers support for upgrading this gem, and related gems such as Webpacker and using Shakapacker. If interested, contact Justin Gordon, justin@shakacode.com. We're also hiring!
Here's a testimonial of how ShakaCode can help, from Florian Gößler of Blinkist, January 2, 2023:
Hey Justin 👋
I just wanted to let you know that we today shipped the webpacker to shakapacker upgrades and it all seems to be running smoothly! Thanks again for all your support and your teams work! 😍
On top of your work, it was now also very easy for me to upgrade Tailwind and include our external node_module based web component library which we were using for our other (more modern) apps already. That work is going to be shipped later this week though as we are polishing the last bits of it. 😉
Have a great 2023 and maybe we get to work together again later in the year! 🙌
After reading this README file, additional information about React-Rails can be found in the Wiki page:
https://github.com/reactjs/React-Rails/wiki
The Wiki page features a significant amount of additional information about React-Rails which includes instructional articles and answers to the most frequently asked questions.
<!-- erb: paste this in view -->
<%= react_component("HelloWorld", { greeting: "Hello from react-rails." }) %>
Lets Start the app:
$ rails s
Output: greeting: Hello from react-rails", inspect webpage in your browser to see the change in tag props.
Run dev server (optional)
In order to run dev server with HMR feature you need to parallely run:
$ ./bin/shakapacker-dev-server
Note: On Rails 6 you need to specify webpack-dev-server host. To this end, update config/initializers/content_security_policy.rb and uncomment relevant lines.
Component name
The component name tells react-rails where to load the component. For example:
react_component call
component require
react_component("Item")
require("Item")
react_component("items/index")
require("items/index")
react_component("items.Index")
require("items").Index
react_component("items.Index.Header")
require("items").Index.Header
This way, you can access top-level, default, or named exports.
The require.context inserted into packs/application.js is used to load components. If you want to load components from a different directory, override it by calling ReactRailsUJS.useContext:
var myCustomContext = require.context("custom_components", true)
varReactRailsUJS = require("react_ujs")
// use `custom_components/` for <%= react_component(...) %> callsReactRailsUJS.useContext(myCustomContext)
In some cases, having multiple require.context entries may be desired. Examples of this include:
Refactoring a typical Rails application into a Rails API with an (eventually) separate Single Page Application (SPA). For this use case, one can add a separate pack in addition to the typical application one. React components can be shared between the packs but the new pack can use a minimal Rails view layout, different default styling, etc.
In a larger application, you might find it helpful to split your JavaScript by routes/controllers to avoid serving unused components and improve your site performance by keeping bundles smaller. For example, you might have separate bundles for homepage, search, and checkout routes. In that scenario, you can add an array of require.context component directory paths via useContexts to server_rendering.js, to allow for Server-Side Rendering across your application:
React-Rails supports plenty of file extensions such as: .js, .jsx.js, .js.jsx, .es6.js, .coffee, etcetera!
Sometimes this will cause a stumble when searching for filenames.
Component File Name
react_component call
app/javascript/components/samplecomponent.js
react_component("samplecomponent")
app/javascript/components/sample_component.js
react_component("sample_component")
app/javascript/components/SampleComponent.js
react_component("SampleComponent")
app/javascript/components/SampleComponent.js.jsx
Has to be renamed to SampleComponent.jsx, then use react_component("SampleComponent")
Typescript support
yarn add typescript @babel/preset-typescript
Babel won’t perform any type-checking on TypeScript code. To optionally use type-checking run:
Doing this will allow React-Rails to support the .tsx extension. Additionally, it is recommended to add ts and tsx to the server_renderer_extensions in your application configuration:
Components must be accessible from the top level, but they may be namespaced, for example:
<%= react_component("Comments.NewForm", {post_id: @post.id}) %>
<!-- looks for `window.Comments.NewForm` -->
Custom JSX Transformer
react-rails uses a transformer class to transform JSX in the asset pipeline. The transformer is initialized once, at boot. You can provide a custom transformer to config.react.jsx_transformer_class. The transformer must implement:
#initialize(options), where options is the value passed to config.react.jsx_transform_options
#transform(code_string) to return a string of transformed code
react-rails provides two transformers, React::JSX::BabelTransformer (which uses ruby-babel-transpiler) and React::JSX::JSXTransformer (which uses the deprecated JSXTransformer.js).
Transform Plugin Options
To supply additional transform plugins to your JSX Transformer, assign them to config.react.jsx_transform_options
react-rails uses the Babel version of the babel-source gem.
For example, to use babel-plugin-transform-class-properties :
By default, React's [development version] is provided to Rails.env.development. You can override the React build with a config:
# Here are the defaults:# config/environments/development.rbMyApp::Application.configure do
config.react.variant = :developmentend# config/environments/production.rbMyApp::Application.configure do
config.react.variant = :productionend
Be sure to restart your Rails server after changing these files. See VERSIONS.md to learn which version of React.js is included with your react-rails version. In some edge cases you may need to bust the sprockets cache with rake tmp:clear
View Helper
react-rails includes a view helper and an unobtrusive JavaScript driver which work together to put React components on the page.
The view helper (react_component) puts a div on the page with the requested component class & props. For example:
**other Any other arguments (eg class:, id:) are passed through to content_tag.
Custom View Helper
react-rails uses a "helper implementation" class to generate the output of the react_component helper. The helper is initialized once per request and used for each react_component call during that request. You can provide a custom helper class to config.react.view_helper_implementation. The class must implement:
#react_component(name, props = {}, options = {}, &block) to return a string to inject into the Rails view
#setup(controller_instance), called when the helper is initialized at the start of the request
#teardown(controller_instance), called at the end of the request
react-rails provides one implementation, React::Rails::ComponentMount.
UJS
react-rails's JavaScript is available as "react_ujs" in the asset pipeline or from NPM. It attaches itself to the window as ReactRailsUJS.
Mounting & Unmounting
Usually, react-rails mounts & unmounts components automatically as described in Event Handling below.
You can also mount & unmount components from <%= react_component(...) %> tags using UJS:
// Mount all components on the page:ReactRailsUJS.mountComponents()
// Mount components within a selector:ReactRailsUJS.mountComponents(".my-class")
// Mount components within a specific node:ReactRailsUJS.mountComponents(specificDOMnode)
// Unmounting works the same way:ReactRailsUJS.unmountComponents()
ReactRailsUJS.unmountComponents(".my-class")
ReactRailsUJS.unmountComponents(specificDOMnode)
You can use this when the DOM is modified by AJAX calls or modal windows.
Event Handling
ReactRailsUJS checks for various libraries to support their page change events:
Turbolinks
pjax
jQuery
Native DOM events
ReactRailsUJS will automatically mount components on <%= react_component(...) %> tags and unmount them when appropriate.
If you need to re-detect events, you can call detectEvents:
// Remove previous event handlers and add new ones:ReactRailsUJS.detectEvents()
For example, if Turbolinks is loaded afterReactRailsUJS, you'll need to call this again. This function removes previous handlers before adding new ones, so it's safe to call as often as needed.
If Turbolinks is imported via Shakapacker (and thus not available globally), ReactRailsUJS will be unable to locate it. To fix this, you can temporarily add it to the global namespace:
// Order is particular. First start Turbolinks:Turbolinks.start();
// Add Turbolinks to the global namespace:window.Turbolinks = Turbolinks;
// Remove previous event handlers and add new ones:ReactRailsUJS.detectEvents();
// (Optional) Clean up global namespace:deletewindow.Turbolinks;
getConstructor
Components are loaded with ReactRailsUJS.getConstructor(className). This function has two default implementations, depending on if you're using the asset pipeline or Shakapacker:
On the asset pipeline, it looks up className in the global namespace (ReactUJS.constructorFromGlobal).
On Shakapacker, it requires files and accesses named exports, as described in Get started with Shakapacker, falling back to the global namespace (ReactUJS.constructorFromRequireContextWithGlobalFallback).
You can override this function to customize the mapping of name-to-constructor. Server-side rendering also uses this function.
For example, the fallback behavior of
ReactUJS.constructorFromRequireContextWithGlobalFallback can sometimes make
server-side rendering errors hard to debug as it will swallow the original error
(more info
here).
ReactUJS.constructorFromRequireContext is provided for this reason. You can
use it like so:
// Replaces calls to `ReactUJS.useContext`ReactUJS.getConstructor = ReactUJS.constructorFromRequireContext(require.context('components', true));
Server-Side Rendering
You can render React components inside your Rails server with prerender: true:
(It will also be mounted by the UJS on page load.)
Server rendering is powered by ExecJS and subject to some requirements:
react-rails must load your code. By convention, it uses server_rendering.js, which was created
by the install task. This file must include your components and their dependencies (eg, Underscore.js).
Requires separate compilations for server & client bundles (see Webpack config)
Your code can't reference document or window. Prerender processes don't have access to document or window,
so jQuery and some other libs won't work in this environment :(
ExecJS supports many backends. CRuby users will get the best performance from mini_racer.
Configuration
Server renderers are stored in a pool and reused between requests. Threaded Rubies (eg jRuby) may see a benefit to increasing the pool size beyond the default 0.
These are the default configurations:
# config/application.rb# These are the defaults if you don't specify any yourselfmoduleMyAppclassApplication < Rails::Application# Settings for the pool of renderers:
config.react.server_renderer_pool_size ||= 1# ExecJS doesn't allow more than one on MRI
config.react.server_renderer_timeout ||= 20# seconds
config.react.server_renderer = React::ServerRendering::BundleRenderer
config.react.server_renderer_options = {
files: ["server_rendering.js"], # files to load for prerenderingreplay_console:true, # if true, console.* will be replayed client-side
}
# Changing files matching these dirs/exts will cause the server renderer to reload:
config.react.server_renderer_extensions = ["jsx", "js"]
config.react.server_renderer_directories = ["/app/assets/javascripts", "/app/javascript/"]
endend
JavaScript State
Some of ExecJS's backends are stateful (eg, mini_racer, therubyracer). This means that any side-effects of a prerender will affect later renders with that renderer.
To manage state, you have a couple options:
Make a custom renderer with #before_render / #after_render hooks as described below
Use per_request_react_rails_prerenderer to manage state for a whole controller action.
To check out a renderer for the duration of a controller action, call the per_request_react_rails_prerenderer helper in the controller class:
classPagesController < ApplicationController# Use the same React server renderer for the entire request:
per_request_react_rails_prerenderer
end
Then, you can access the ExecJS context directly with react_rails_prerenderer.context:
defshow
react_rails_prerenderer # => #<React::ServerRendering::BundleRenderer>
react_rails_prerenderer.context # => #<ExecJS::Context># Execute arbitrary JavaScript code# `self` is the global context
react_rails_prerenderer.context.exec("self.Store.setup()")
render :show
react_rails_prerenderer.context.exec("self.Store.teardown()")
end
react_rails_prerenderer may also be accessed in before- or after-actions.
Custom Server Renderer
react-rails depends on a renderer class for rendering components on the server. You can provide a custom renderer class to config.react.server_renderer. The class must implement:
#initialize(options={}), which accepts the hash from config.react.server_renderer_options
#render(component_name, props, prerender_options) to return a string of HTML
react-rails provides two renderer classes: React::ServerRendering::ExecJSRenderer and React::ServerRendering::BundleRenderer.
ExecJSRenderer offers two other points for extension:
#before_render(component_name, props, prerender_options) to return a string of JavaScript to execute before calling React.render
#after_render(component_name, props, prerender_options) to return a string of JavaScript to execute after calling React.render
Any subclass of ExecJSRenderer may use those hooks (for example, BundleRenderer uses them to handle console.* on the server).
Controller Actions
Components can also be server-rendered directly from a controller action with the custom component renderer. For example:
You can also provide the "usual" render arguments: content_type, layout, location and status. By default, your current layout will be used and the component, rather than a view, will be rendered in place of yield. Custom data-* attributes can be passed like data: {remote: true}.
Prerendering is set to true by default, but can be turned off with prerender: false.
Component Generator
You can generate a new component file with:
rails g react:component ComponentName prop1:type prop2:type ... [options]
For example,
rails g react:component Post title:string published:bool published_by:instanceOf{Person}
instanceOf takes an optional class name in the form of instanceOf{className}.
oneOf behaves like an enum, and takes an optional list of strings in the form of 'name:oneOf{one,two,three}'.
oneOfType takes an optional list of react and custom types in the form of 'model:oneOfType{string,number,OtherType}'.
Note that the arguments for oneOf and oneOfType must be enclosed in single quotes
to prevent your terminal from expanding them into an argument list.
Use with JBuilder
If you use Jbuilder to pass a JSON string to react_component, make sure your JSON is a stringified hash,
not an array. This is not the Rails default -- you should add the root node yourself. For example:
# BAD: returns a stringified array
json.array!(@messages) do |message|
json.extract! message, :id, :name
json.url message_url(message, format::json)
end# GOOD: returns a stringified hash
json.messages(@messages) do |message|
json.extract! message, :id, :name
json.url message_url(message, format::json)
end
Camelize Props
You can configure camelize_props option:
MyApp::Application.configure do
config.react.camelize_props = true# default falseend
Now, Ruby hashes given to react_component(...) as props will have their keys transformed from underscore- to camel-case, for example:
To make simple changes to Component templates, copy the respective template file to your Rails project at lib/templates/react/component/template_filename.
For example, to change the ES6 Component template, copy it to lib/templates/react/component/component.es6.jsx and modify it.
Upgrading
2.7 to 3.0
Keep your react_ujs up to date: yarn upgrade
Drop support for Webpacker: Before any ReactRails upgrade, make sure upgrading from Webpacker to Shakapacker 7. For more information check out Shakapacker
SSR: ReactRails 3.x requires separate compilations for server & client bundles. See Webpack config directory in the dummy app to addapt the new implementation.
2.3 to 2.4
Keep your react_ujs up to date, yarn upgrade
React-Rails 2.4.x uses React 16+ which no longer has React Addons. Therefore the pre-bundled version of react no longer has an addons version, if you need addons still, there is the 2.3.1+ version of the gem that still has addons.
If you need to make changes in your components for the prebundled react, see the migration docs here:
Getting warning for Can't resolve 'react-dom/client' in React < 18
You may see a warning like this when building a Webpack bundle using any version of React below 18. This warning can be safely suppressed in your Webpack configuration. The following is an example of this suppression in config/webpack/webpack.config.js:
TheRubyRacer hasn't updated LibV8 (The library that powers Node.js) from v3 in 2 years, any new features are unlikely to work.
LibV8 itself is already beyond version 7 therefore many serverside issues are caused by old JS engines and fixed by using an up to date one such as MiniRacer or TheRubyRhino on JRuby.
Ruby Hyperstack: Use Ruby to build reactive user interfaces with React.
Contributing
🎉 Thanks for taking the time to contribute! 🎉
With 5 Million+ downloads of the react-rails Gem and another 2 Million+ downloads of react_ujs on NPM, you're helping the biggest React + Rails community!
By contributing to React-Rails, you agree to abide by the code of conduct.
You can always help by submitting patches or triaging issues. Even offering reproduction steps to issues is incredibly helpful!
Supporters
The following companies support the development of this and other open-source projects maintained by ShakaCode by providing licenses to the ShakaCode team. ShakaCode stands by the usefulness of these products!
We found that react_ujs demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.It has 3 open source maintainers collaborating on the project.
Package last updated on 16 Aug 2023
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
OpenSSF has published OSPS Baseline, an initiative designed to establish a minimum set of security-related best practices for open source software projects.
Michigan TypeScript founder Dimitri Mitropoulos implements WebAssembly runtime in TypeScript types, enabling Doom to run after processing 177 terabytes of type definitions.