# react-rails [![Gem](https://img.shields.io/gem/v/react-rails.svg?style=flat-square)](http://rubygems.org/gems/react-rails) [![Build Status](https://img.shields.io/travis/reactjs/react-rails/master.svg?style=flat-square)](https://travis-ci.org/reactjs/react-rails) [![Gemnasium](https://img.shields.io/gemnasium/reactjs/react-rails.svg?style=flat-square)](https://gemnasium.com/reactjs/react-rails) [![Code Climate](https://img.shields.io/codeclimate/github/reactjs/react-rails.svg?style=flat-square)](https://codeclimate.com/github/reactjs/react-rails) [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/reactjs/react-rails.svg?style=flat-square)](https://codeclimate.com/github/reactjs/react-rails/coverage) `react-rails` makes it easy to use [React](http://facebook.github.io/react/) and [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) in your Ruby on Rails (3.2+) application. Learn more: - React's [Getting Started guide](https://facebook.github.io/react/docs/getting-started.html) - Use React & JSX [with webpacker](#use-with-webpacker) or [with the asset pipeline](#use-with-asset-pipeline) - Rendering [components in views](#view-helper) or [in controller actions](#controller-actions) - [Server-side rendering](#server-side-rendering) - [Generating components](#component-generator) in various formats - [`ReactRailsUJS`](#ujs) for mounting and unmounting components - Automatically [camelizing props](#camelize-props) - [Related Projects](#related-projects) - [Developing](#development) the gem ## Installation Install from Rubygems as `react-rails`. ```ruby gem "react-rails" ``` Get started with `rails g react:install`: ``` $ rails g react:install ``` ## Use with Webpacker [webpacker](https://github.com/rails/webpacker) integrates modern JS tooling with Rails. `ReactRailsUJS` allows you to gradually migrate to webpacker. Get started by adding `webpacker` to your gemfile and installing `webpacker` and `react-rails`: ``` $ rails webpacker:install $ rails webpacker:install:react $ rails generate react:install ``` This gives you: - `components/` directory for your React components - [`ReactRailsUJS`](#ujs) setup in `packs/application.js` - `packs/server_rendering.js` for [server-side rendering](#server-side-rendering) When you add a component to `components/`, you can [render it in a Rails view](#view-helper): ```erb <%= react_component("HelloWorld", { greeting: "Hello" }) %> ``` 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. If `require` fails, `react-rails` falls back to the global namespace approach described in [Use with Asset Pipeline](#use-with-asset-pipeline). 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`: ```js var myCustomContext = require.context("custom_components", true) var ReactRailsUJS = require("react_ujs") // use `custom_components/` for <%= react_component(...) %> calls ReactRailsUJS.useContext(myCustomContext) ``` ## Use with Asset Pipeline `react-rails` provides React.js & a UJS driver to the Rails asset pipeline. Get started by installing: ``` $ 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](#server-side-rendering) Now, you can create React components in `.jsx` files: ```js // app/assets/javascripts/components/post.jsx window.Post = React.createClass({ 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](#view-helper): ```erb <%= react_component("Post", {title: "Hello World"}) %> ``` Components must be accessible from the top level, but they may be namespaced, for example: ```erb <%= react_component("Comments.NewForm", {post_id: @post.id}) %> ``` ### 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](https://github.com/babel/ruby-babel-transpiler)) and `React::JSX::JSXTransformer` (which uses the deprecated `JSXTransformer.js`). ### React.js versions `//= require react` brings `React` into your project. To include `React.addons`, add this config: ```ruby # config/application.rb MyApp::Application.configure do config.react.addons = true # defaults to false end ``` By default, React's [development version] is provided to `Rails.env.development`. You can override the React build with a config: ```ruby # 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](https://github.com/reactjs/react-rails/blob/master/VERSIONS.md) to learn which version of React.js is included with your `react-rails` version. ## View Helper `react-rails` includes a view helper and an [unobtrusive JavaScript driver](#ujs) 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: ```erb <%= react_component('HelloMessage', name: 'John') %>
``` On page load, the [`react_ujs` driver](#ujs) will scan the page and mount components using `data-react-class` and `data-react-props`. The view helper's signature is: ```ruby react_component(component_class_name, props={}, html_options={}) ``` - `component_class_name` is a string which identifies a component. See [getConstructor](#getconstructor) for details. - `props` is either: - an object that responds to `#to_json`; or - an already-stringified JSON object (see [JBuilder note](#use-with-jbuilder) 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](#camelize_props) - `**other` Any other arguments (eg `class:`, `id:`) are passed through to [`content_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-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](#event-handling) below. You can also mount & unmount components from `<%= react_component(...) %>` tags using UJS: ```js // 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. Be sure to load `react_ujs` _after_ these libraries so that it can detect them. ### `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 [Use with Webpacker](#use-with-webpacker). You can override this function to customize the mapping of name-to-constructor. [Server-side rendering](#server-side-rendering) also uses this function. ## Server-Side Rendering You can render React components inside your Rails server with `prerender: true`: ```erb <%= react_component('HelloMessage', {name: 'John'}, {prerender: true}) %>

Hello, John!

``` _(It will also be mounted by the [UJS](#ujs) on page load.)_ Server rendering is powered by [`ExecJS`](https://github.com/rails/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`](https://github.com/discourse/mini_racer#performance). #### 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: ```ruby # config/environments/application.rb # These are the defaults if you don't specify any yourself MyApp::Application.configure do # 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/javascripts/"] 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](#custom-server-renderer) - 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: ```ruby 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`: ```ruby def show react_rails_prerenderer # => # react_rails_prerenderer.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: ```ruby 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: ```sh rails g react:component ComponentName prop1:type prop2:type ... ``` For example, ```sh rails g react:component Post title:string published:bool published_by:instanceOf{Person} ``` would generate: ```js var Post = React.createClass({ propTypes: { title: React.PropTypes.string, published: React.PropTypes.bool, publishedBy: React.PropTypes.instanceOf(Person) }, render: function() { return (
Title: {this.props.title}
Published: {this.props.published}
Published By: {this.props.publishedBy}
); } }); ``` 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: ```ruby # 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: ```ruby 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: ```ruby { all_todos: @todos, current_status: @status } # becomes: { "allTodos" => @todos, "currentStatus" => @status } ``` You can also specify this option in `react_component`: ```erb <%= react_component('HelloMessage', {name: 'John'}, {camelize_props: true}) %> ``` ## Related Projects - [react\_on\_rails Gem](https://github.com/shakacode/react_on_rails): Integration of React with Rails utilizing Webpack, Babel, React, Redux, React-Router. - [Ruby Hyperloop](http://ruby-hyperloop.io/): Use Ruby to build reactive user interfaces with React. - [react-rails-hot-loader](https://github.com/rmosolgo/react-rails-hot-loader) is a simple live-reloader for `react-rails`. - [react-rails-benchmark_renderer](https://github.com/pboling/react-rails-benchmark_renderer) adds performance instrumentation to server rendering. - [The Free React on Rails Course](https://learnetto.com/users/hrishio/courses/the-free-react-on-rails-5-course) A free video course which teaches the basics of React and how to get started using it in Rails with `react-rails`. ## Development - Run tests with `rake test` or `appraisal rake test` - Update React assets with `rake react:update` - Update the UJS with `rake ujs:update`