react-rails

by reactjs

reactjs / react-rails

Integrate React.js with Rails views and controllers, the asset pipeline, or webpacker.

6.1K Stars 706 Forks Last release: over 1 year ago (v2.6.0) Apache License 2.0 1.1K Commits 55 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

React-Rails

Gem npm Build Status Maintainers Wanted

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

Contents

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.

Get started with Webpacker

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:

1) Create a new Rails app:
$ rails new my-app
$ cd my-app
2) Add
webpacker
and
react-rails
to your gemfile:
gem 'webpacker'
gem 'react-rails'
3) Now run the installers:
Rails 6.x:
$ 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_tag
as in Step 4. Since its already added by default.
Rails 5.x:
$ 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
  • ReactRailsUJS
    setup in
    app/javascript/packs/application.js
  • app/javascript/packs/server_rendering.js
    for server-side rendering
4) Link the JavaScript pack in Rails view using
javascript_pack_tag
helper:

5) Generate your first component:
$ rails g react:component HelloWorld greeting:string
6) You can also generate your component in a subdirectory:
$ 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:

7) Render it in a Rails view:

8) Lets Start the app:
$ rails s

output: greeting: Hello from react-rails", inspect webpage in your browser too see change in tag props.

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)
var ReactRailsUJS = require("react_ujs")
// use `custom_components/` for  calls
ReactRailsUJS.useContext(myCustomContext)

If

require
fails to find your component,
ReactRailsUJS
falls back to the global namespace, described in Use with Asset Pipeline.

File naming

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

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

ts
and
tsx
to the
server_renderer_extensions
in your application configuration:
config.react.server_renderer_extensions = ["jsx", "js", "tsx", "ts"]

Test component

You can use

assert_react_component
to 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

Use with Asset Pipeline

react-rails
provides a pre-bundled React.js & a UJS driver to the Rails asset pipeline. Get started by adding the
react-rails
gem:
gem 'react-rails'

And then install the react generator:

$ rails g react:install

Then restart your development server.

This will:

  • add some
    //= require
    s to
    application.js
  • add a
    components/
    directory for React components
  • add
    server_rendering.js
    for server-side rendering

Now, you can create React components in

.jsx
files:
// app/assets/javascripts/components/post.jsx

window.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:


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
:
config.react.jsx_transform_options = {
  optional: ['es7.classProperties']
}

React.js versions

//= require react
brings
React
into 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
end

config/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-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:

On page load, the

react_ujs
driver will scan the page and mount components using

data-react-class
and
data-react-props
.

The view helper's signature is:

react_component(component_class_name, props={}, html_options={})
  • component_class_name
    is a string which identifies a component. See getConstructor for details.
  • props
    is either:
    • an object that responds to
      #to_json
      ; or
    • an already-stringified JSON object (see JBuilder note below).
  • html_options
    may include:
    • tag:
      to use an element other than a
      div
      to embed
      data-react-class
      and
      data-react-props
      .
    • prerender: true
      to render the component on the server.
    • camelize_props
      to transform a props hash
    • **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

 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
 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 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

Turbolinks
is
import
ed via Webpacker (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:
delete window.Turbolinks;

getConstructor

Components are loaded with

ReactRailsUJS.getConstructor(className)
. This function has two built-in implementations:
  • On the asset pipeline, it looks up
    className
    in the global namespace.
  • On Webpacker, it
    require
    s 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.

Server-Side Rendering

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

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).
  • 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 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

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:
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 context

react_rails_prerenderer.context.exec("self.Store.setup()") render :show react_rails_prerenderer.context.exec("self.Store.teardown()") end </:context></:serverrendering::bundlerenderer>

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:
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"

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 ...

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:

  • Plain types:
    any
    ,
    array
    ,
    bool
    ,
    element
    ,
    func
    ,
    number
    ,
    object
    ,
    node
    ,
    shape
    ,
    string
  • 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 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
:

Upgrading

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:

  • https://reactjs.org/blog/2016/11/16/react-v15.4.0.html
  • https://reactjs.org/blog/2017/04/07/react-v15.5.0.html
  • https://reactjs.org/blog/2017/06/13/react-v15.6.0.html

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:react
to update npm packages (Webpacker only)

Common Errors

During installation

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 ```

Undefined Set

ExecJS::ProgramError (identifier 'Set' undefined):

(execjs):1

If you see any variation of this issue, see Using TheRubyRacer

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.

HMR

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.

Related Projects

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!

Please see our Contribution guide for more info.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.