Integrate React.js with Rails views and controllers, the asset pipeline, or webpacker.
React-Rails is a flexible tool to use React with Rails. The benefits: * Automatically renders React server-side and client-side * Supports Webpacker 4.x, 3.x, 2.x, 1.1+ * Supports Sprockets 4.x, 3.x, 2.x * Lets you use JSX, ES6, TypeScript, CoffeeScript
A source code example utilizing React-Rails: https://github.com/BookOfGreg/react-rails-example-app
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.
Alternatively, get started with Sprockets
Webpacker provides modern JS tooling for Rails. Here are the listed steps for integrating Webpacker and Rails-React with Rails:
$ rails new my-app $ cd my-app
webpackerand
react-railsto your gemfile:
gem 'webpacker' gem 'react-rails'
$ bundle install $ rails webpacker:install $ rails webpacker:install:react $ rails generate react:install
Note: For Rails 6, You don't need to add
javascript_pack_tagas in Step 4. Since its already added by default.
$ bundle install $ rails webpacker:install # OR (on rails version < 5.0) rake webpacker:install $ rails webpacker:install:react # OR (on rails version < 5.0) rake webpacker:install:react $ rails generate react:install
This gives you:
app/javascript/components/directory for your React components
ReactRailsUJSsetup in
app/javascript/packs/application.js
app/javascript/packs/server_rendering.jsfor server-side rendering
javascript_pack_taghelper:
$ rails g react:component HelloWorld greeting:string
$ rails g react:component my_subdirectory/HelloWorld greeting:string
Note: Your component is added to
app/javascript/components/by default.
Note: If your component is in a subdirectory you will append the directory path to your erb component call.
Example:
$ rails s
output: greeting: Hello from react-rails", inspect webpage in your browser too see change in tag props.
The component name tells
react-railswhere to load the component. For example:
react_componentcall |
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.contextinserted into
packs/application.jsis 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) var ReactRailsUJS = require("react_ujs") // use `custom_components/` for calls ReactRailsUJS.useContext(myCustomContext)
If
requirefails to find your component,
ReactRailsUJSfalls back to the global namespace, described in Use with Asset Pipeline.
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_componentcall |
---|
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")
If you want to use React-Rails with Typescript, simply run the installer and add @types:
$ bundle exec rails webpacker:install:typescript $ yarn add @types/react @types/react-dom
Doing this will allow React-Rails to support the .tsx extension. Additionally, it is recommended to add
tsand
tsxto the
server_renderer_extensionsin your application configuration:
config.react.server_renderer_extensions = ["jsx", "js", "tsx", "ts"]
You can use
assert_react_componentto test component render:
app/views/welcome/index.html.erb
class WelcomeControllerTest < ActionDispatch::IntegrationTest test 'assert_react_component' do get "/welcome" assert_equal 200, response.status# assert rendered react component and check the props assert_react_component "HelloWorld" do |props| assert_equal "Hello from react-rails.", props[:greeting] assert_equal "react-rails", props[:info][:name] assert_select "[class=?]", "hello-world" end # or just assert component rendered assert_react_component "HelloWorld"
end end
react-railsprovides a pre-bundled React.js & a UJS driver to the Rails asset pipeline. Get started by adding the
react-railsgem:
gem 'react-rails'
And then install the react generator:
$ rails g react:install
Then restart your development server.
This will:
//= requires to
application.js
components/directory for React components
server_rendering.jsfor server-side rendering
Now, you can create React components in
.jsxfiles:
// app/assets/javascripts/components/post.jsxwindow.Post = createReactClass({ render: function() { return
{this.props.title}
} })// or, equivalent: class Post extends React.Component { render() { return
{this.props.title}
} }
Then, you can render those components in views:
Components must be accessible from the top level, but they may be namespaced, for example:
react-railsuses 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-railsprovides two transformers,
React::JSX::BabelTransformer(which uses ruby-babel-transpiler) and
React::JSX::JSXTransformer(which uses the deprecated
JSXTransformer.js).
To supply additional transform plugins to your JSX Transformer, assign them to
config.react.jsx_transform_options
react-railsuses the Babel version of the
babel-sourcegem.
For example, to use
babel-plugin-transform-class-properties:
config.react.jsx_transform_options = { optional: ['es7.classProperties'] }
//= require reactbrings
Reactinto your project.
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.rb MyApp::Application.configure do config.react.variant = :development endconfig/environments/production.rb
MyApp::Application.configure do config.react.variant = :production end
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-railsversion. In some edge cases you may need to bust the sprockets cache with
rake tmp:clear
react-railsincludes 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
divon the page with the requested component class & props. For example:
react_ujsdriver will scan the page and mount components using
data-react-classand
data-react-props.
The view helper's signature is:
react_component(component_class_name, props={}, html_options={})
component_class_nameis a string which identifies a component. See getConstructor for details.
propsis either:
#to_json; or
html_optionsmay include:
tag:to use an element other than a
divto embed
data-react-classand
data-react-props.
prerender: trueto render the component on the server.
camelize_propsto transform a props hash
**otherAny other arguments (eg
class:,
id:) are passed through to
content_tag.
react-railsuses a "helper implementation" class to generate the output of the
react_componenthelper. The helper is initialized once per request and used for each
react_componentcall 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-railsprovides one implementation,
React::Rails::ComponentMount.
react-rails's JavaScript is available as
"react_ujs"in the asset pipeline or from NPM. It attaches itself to the window as
ReactRailsUJS.
Usually,
react-railsmounts & unmounts components automatically as described in Event Handling below.
You can also mount & unmount components from
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.
ReactRailsUJSchecks for various libraries to support their page change events:
Turbolinks
pjax
jQuery
ReactRailsUJSwill automatically mount components on 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
Turbolinksis loaded after
ReactRailsUJS, 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
Turbolinksis
imported via Webpacker (and thus not available globally),
ReactRailsUJSwill 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: delete window.Turbolinks;
getConstructor
Components are loaded with
ReactRailsUJS.getConstructor(className). This function has two built-in implementations:
classNamein the global namespace.
requires files and accesses named exports, as described in Get started with Webpacker.
You can override this function to customize the mapping of name-to-constructor. Server-side rendering also uses this function.
You can render React components inside your Rails server with
prerender: true:
Hello, John!
(It will also be mounted by the UJS on page load.)
Server rendering is powered by
ExecJSand subject to some requirements:
react-railsmust 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).
documentor
window. Prerender processes don't have access to
documentor
window, so jQuery and some other libs won't work in this environment :(
ExecJSsupports many backends. CRuby users will get the best performance from
mini_racer.
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 yourself module MyApp class Application < 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 prerendering replay_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/"] end end
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:
#before_render/
#after_renderhooks as described below
per_request_react_rails_prerendererto 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_prerendererhelper in the controller class:
class PagesController < 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:
def show react_rails_prerenderer # => #<:serverrendering::bundlerenderer> react_rails_prerenderer.context # => #<:context>Execute arbitrary JavaScript code
self
is the global contextreact_rails_prerenderer.context.exec("self.Store.setup()") render :show react_rails_prerenderer.context.exec("self.Store.teardown()") end </:context></:serverrendering::bundlerenderer>
react_rails_prerenderermay also be accessed in before- or after-actions.
react-railsdepends 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-railsprovides two renderer classes:
React::ServerRendering::ExecJSRendererand
React::ServerRendering::BundleRenderer.
ExecJSRendereroffers 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
ExecJSRenderermay use those hooks (for example,
BundleRendereruses them to handle
console.*on the server).
Components can also be server-rendered directly from a controller action with the custom
componentrenderer. For example:
class TodoController < ApplicationController def index @todos = Todo.all render component: 'TodoList', props: { todos: @todos }, tag: 'span', class: 'todo' end end
You can also provide the "usual"
renderarguments:
content_type,
layout,
locationand
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
trueby default, but can be turned off with
prerender: false.
You can generate a new component file with:
rails g react:component ComponentName prop1:type prop2:type ...
For example,
rails g react:component Post title:string published:bool published_by:instanceOf{Person}
would generate:
var Post = createReactClass({ propTypes: { title: PropTypes.string, published: PropTypes.bool, publishedBy: PropTypes.instanceOf(Person) },render: function() { return ( <react.fragment> Title: {this.props.title} Published: {this.props.published} Published By: {this.props.publishedBy} </react.fragment> ); } });
The generator also accepts options:
--es6: use
class ComponentName extends React.Component
--coffee: use CoffeeScript
Accepted PropTypes are:
any,
array,
bool,
element,
func,
number,
object,
node,
shape,
string
instanceOftakes an optional class name in the form of
instanceOf{className}.
oneOfbehaves like an enum, and takes an optional list of strings in the form of
'name:oneOf{one,two,three}'.
oneOfTypetakes an optional list of react and custom types in the form of
'model:oneOfType{string,number,OtherType}'.
Note that the arguments for
oneOfand
oneOfTypemust be enclosed in single quotes to prevent your terminal from expanding them into an argument list.
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) endGOOD: returns a stringified hash
json.messages(@messages) do |message| json.extract! message, :id, :name json.url message_url(message, format: :json) end
You can configure
camelize_propsoption:
MyApp::Application.configure do config.react.camelize_props = true # default false end
Now, Ruby hashes given to
react_component(...)as props will have their keys transformed from underscore- to camel-case, for example:
{ all_todos: @todos, current_status: @status } # becomes: { "allTodos" => @todos, "currentStatus" => @status }
You can also specify this option in
react_component:
Keep your
react_ujsup 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:
For the vast majority of cases this will get you most of the migration: - global find+replace
React.Prop->
Prop- add
import PropTypes from 'prop-types'(Webpacker only) - re-run
bundle exec rails webpacker:install:reactto update npm packages (Webpacker only)
1) While using installers.(rails webpacker:install:react && rails webpacker:install) Error:
public/packs/manifest.json. Possible causes: 1. You want to set webpacker.yml value of compile to true for your environment unless you are using the `webpack -w` or the webpack-dev-server. 2. webpack has not yet re-run to reflect updates. 3. You have misconfigured Webpacker's config/webpacker.yml file. 4. Your webpack configuration is not creating a manifest. or yarn: error: no such option: --dev ERROR: [Errno 2] No such file or directory: 'add'Fix: Try updating yarn package. ``` sudo apt remove cmdtest sudo apt remove yarn curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get install yarn
yarn install ```
ExecJS::ProgramError (identifier 'Set' undefined):(execjs):1
If you see any variation of this issue, see Using TheRubyRacer
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.
Hot Module Replacement is possible with this gem as it does just pass through to Webpacker. Please open an issue to let us know tips and tricks for it to add to the wiki.
Sample repo that shows HMR working with
react-rails: https://github.com/edelgado/react-rails-hmr
One caveat is that currently you cannot Server-Side Render along with HMR.
react-rails.
react-rails.
🎉 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!
Please see our Contribution guide for more info.