UNPKG

19.9 kBMarkdownView Raw
1# react-rails
2
3[![Gem](https://img.shields.io/gem/v/react-rails.svg?style=flat-square)](http://rubygems.org/gems/react-rails)
4[![Build Status](https://img.shields.io/travis/reactjs/react-rails/master.svg?style=flat-square)](https://travis-ci.org/reactjs/react-rails)
5[![Gemnasium](https://img.shields.io/gemnasium/reactjs/react-rails.svg?style=flat-square)](https://gemnasium.com/reactjs/react-rails)
6[![Code Climate](https://img.shields.io/codeclimate/github/reactjs/react-rails.svg?style=flat-square)](https://codeclimate.com/github/reactjs/react-rails)
7[![Test Coverage](https://img.shields.io/codeclimate/coverage/github/reactjs/react-rails.svg?style=flat-square)](https://codeclimate.com/github/reactjs/react-rails/coverage)
8
9You are looking at the 2.3 stable branch. This branch is for maintaining the prebundled 15.6 ReactJS with Addons. Gem version 2.4.x onwards and master will no longer have React Addons.
10
11If you need to make changes for the prebundled react, see the migration docs here:
12https://reactjs.org/blog/2016/11/16/react-v15.4.0.html
13https://reactjs.org/blog/2017/04/07/react-v15.5.0.html
14https://reactjs.org/blog/2017/06/13/react-v15.6.0.html
15
16`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:
17
18- React's [Getting Started guide](https://facebook.github.io/react/docs/getting-started.html)
19- Use React & JSX [with webpacker](#use-with-webpacker) or [with the asset pipeline](#use-with-asset-pipeline)
20- Rendering [components in views](#view-helper) or [in controller actions](#controller-actions)
21- [Server-side rendering](#server-side-rendering)
22- [Generating components](#component-generator) in various formats
23- [`ReactRailsUJS`](#ujs) for mounting and unmounting components
24- Automatically [camelizing props](#camelize-props)
25- [Related Projects](#related-projects)
26- [Developing](#development) the gem
27
28## Installation
29
30Install from Rubygems as `react-rails`.
31
32```ruby
33gem "react-rails"
34```
35
36Get started with `rails g react:install`:
37
38```
39$ rails g react:install
40```
41
42## Use with Webpacker
43
44[webpacker](https://github.com/rails/webpacker) integrates modern JS tooling with Rails. `ReactRailsUJS` allows you to gradually migrate to webpacker.
45
46Get started by adding `webpacker` to your gemfile and installing `webpacker` and `react-rails`:
47
48```
49$ rails webpacker:install
50$ rails webpacker:install:react
51$ rails generate react:install
52```
53
54This gives you:
55
56- `components/` directory for your React components
57- [`ReactRailsUJS`](#ujs) setup in `packs/application.js`
58- `packs/server_rendering.js` for [server-side rendering](#server-side-rendering)
59
60When you add a component to `components/`, you can [render it in a Rails view](#view-helper):
61
62```erb
63<%= react_component("HelloWorld", { greeting: "Hello" }) %>
64```
65
66The component name tells `react-rails` where to load the component. For example:
67
68`react_component` call | component `require`
69-----|-----
70`react_component("Item")` | `require("Item")`
71`react_component("items/index")` | `require("items/index")`
72`react_component("items.Index")` | `require("items").Index`
73`react_component("items.Index.Header")` | `require("items").Index.Header`
74
75This way, you can access top-level, default, or named exports.
76
77If `require` fails, `react-rails` falls back to the global namespace approach described in [Use with Asset Pipeline](#use-with-asset-pipeline).
78
79The `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`:
80
81```js
82var myCustomContext = require.context("custom_components", true)
83var ReactRailsUJS = require("react_ujs")
84// use `custom_components/` for <%= react_component(...) %> calls
85ReactRailsUJS.useContext(myCustomContext)
86```
87
88## Use with Asset Pipeline
89
90`react-rails` provides React.js & a UJS driver to the Rails asset pipeline. Get started by installing:
91
92```
93$ rails g react:install
94```
95
96Then restart your development server.
97
98This will:
99
100- add some `//= require`s to `application.js`
101- add a `components/` directory for React components
102- add `server_rendering.js` for [server-side rendering](#server-side-rendering)
103
104Now, you can create React components in `.jsx` files:
105
106```js
107// app/assets/javascripts/components/post.jsx
108
109window.Post = createReactClass({
110 render: function() {
111 return <h1>{this.props.title}</h1>
112 }
113})
114
115// or, equivalent:
116class Post extends React.Component {
117 render() {
118 return <h1>{this.props.title}</h1>
119 }
120}
121```
122
123Then, you can render those [components in views](#view-helper):
124
125```erb
126<%= react_component("Post", {title: "Hello World"}) %>
127```
128
129Components must be accessible from the top level, but they may be namespaced, for example:
130
131```erb
132<%= react_component("Comments.NewForm", {post_id: @post.id}) %>
133<!-- looks for `window.Comments.NewForm` -->
134```
135
136### Custom JSX Transformer
137
138`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:
139
140- `#initialize(options)`, where options is the value passed to `config.react.jsx_transform_options`
141- `#transform(code_string)` to return a string of transformed code
142
143`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`).
144
145### React.js versions
146
147`//= require react` brings `React` into your project.
148
149By default, React's [development version] is provided to `Rails.env.development`. You can override the React build with a config:
150
151```ruby
152# Here are the defaults:
153# config/environments/development.rb
154MyApp::Application.configure do
155 config.react.variant = :development
156end
157
158# config/environments/production.rb
159MyApp::Application.configure do
160 config.react.variant = :production
161end
162```
163
164Be 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.
165
166
167## View Helper
168
169`react-rails` includes a view helper and an [unobtrusive JavaScript driver](#ujs) which work together to put React components on the page.
170
171The view helper (`react_component`) puts a `div` on the page with the requested component class & props. For example:
172
173```erb
174<%= react_component('HelloMessage', name: 'John') %>
175<!-- becomes: -->
176<div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}"></div>
177```
178
179On page load, the [`react_ujs` driver](#ujs) will scan the page and mount components using `data-react-class`
180and `data-react-props`.
181
182The view helper's signature is:
183
184```ruby
185react_component(component_class_name, props={}, html_options={})
186```
187
188- `component_class_name` is a string which identifies a component. See [getConstructor](#getconstructor) for details.
189- `props` is either:
190 - an object that responds to `#to_json`; or
191 - an already-stringified JSON object (see [JBuilder note](#use-with-jbuilder) below).
192- `html_options` may include:
193 - `tag:` to use an element other than a `div` to embed `data-react-class` and `data-react-props`.
194 - `prerender: true` to render the component on the server.
195 - `camelize_props` to [transform a props hash](#camelize-props)
196 - `**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).
197
198
199#### Custom View Helper
200
201`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:
202
203- `#react_component(name, props = {}, options = {}, &block)` to return a string to inject into the Rails view
204- `#setup(controller_instance)`, called when the helper is initialized at the start of the request
205- `#teardown(controller_instance)`, called at the end of the request
206
207`react-rails` provides one implementation, `React::Rails::ComponentMount`.
208
209## UJS
210
211`react-rails`'s JavaScript is available as `"react_ujs"` in the asset pipeline or from NPM. It attaches itself to the window as `ReactRailsUJS`.
212
213### Mounting & Unmounting
214
215Usually, `react-rails` mounts & unmounts components automatically as described in [Event Handling](#event-handling) below.
216
217You can also mount & unmount components from `<%= react_component(...) %>` tags using UJS:
218
219```js
220// Mount all components on the page:
221ReactRailsUJS.mountComponents()
222// Mount components within a selector:
223ReactRailsUJS.mountComponents(".my-class")
224// Mount components within a specific node:
225ReactRailsUJS.mountComponents(specificDOMnode)
226
227// Unmounting works the same way:
228ReactRailsUJS.unmountComponents()
229ReactRailsUJS.unmountComponents(".my-class")
230ReactRailsUJS.unmountComponents(specificDOMnode)
231```
232
233You can use this when the DOM is modified by AJAX calls or modal windows.
234
235### Event Handling
236
237`ReactRailsUJS` checks for various libraries to support their page change events:
238
239- `Turbolinks`
240- `pjax`
241- `jQuery`
242- Native DOM events
243
244`ReactRailsUJS` will automatically mount components on `<%= react_component(...) %>` tags and unmount them when appropriate.
245
246If you need to re-detect events, you can call `detectEvents`:
247
248```js
249// Remove previous event handlers and add new ones:
250ReactRailsUJS.detectEvents()
251```
252
253For 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.
254
255### `getConstructor`
256
257Components are loaded with `ReactRailsUJS.getConstructor(className)`. This function has two built-in implementations:
258
259- On the asset pipeline, it looks up `className` in the global namespace.
260- On webpacker, it `require`s files and accesses named exports, as described in [Use with Webpacker](#use-with-webpacker).
261
262You can override this function to customize the mapping of name-to-constructor. [Server-side rendering](#server-side-rendering) also uses this function.
263
264## Server-Side Rendering
265
266You can render React components inside your Rails server with `prerender: true`:
267
268```erb
269<%= react_component('HelloMessage', {name: 'John'}, {prerender: true}) %>
270<!-- becomes: -->
271<div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}">
272 <h1>Hello, John!</h1>
273</div>
274```
275
276_(It will also be mounted by the [UJS](#ujs) on page load.)_
277
278Server rendering is powered by [`ExecJS`](https://github.com/rails/execjs) and subject to some requirements:
279
280- `react-rails` must load your code. By convention, it uses `server_rendering.js`, which was created
281by the install task. This file must include your components _and_ their dependencies (eg, Underscore.js).
282- Your code can't reference `document` or `window`. Prerender processes don't have access to `document` or `window`,
283so jQuery and some other libs won't work in this environment :(
284
285`ExecJS` supports many backends. CRuby users will get the best performance from [`mini_racer`](https://github.com/discourse/mini_racer#performance).
286
287#### Configuration
288
289Server 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`.
290
291These are the default configurations:
292
293```ruby
294# config/environments/application.rb
295# These are the defaults if you don't specify any yourself
296MyApp::Application.configure do
297 # Settings for the pool of renderers:
298 config.react.server_renderer_pool_size ||= 1 # ExecJS doesn't allow more than one on MRI
299 config.react.server_renderer_timeout ||= 20 # seconds
300 config.react.server_renderer = React::ServerRendering::BundleRenderer
301 config.react.server_renderer_options = {
302 files: ["server_rendering.js"], # files to load for prerendering
303 replay_console: true, # if true, console.* will be replayed client-side
304 }
305 # Changing files matching these dirs/exts will cause the server renderer to reload:
306 config.react.server_renderer_extensions = ["jsx", "js"]
307 config.react.server_renderer_directories = ["/app/assets/javascripts", "/app/javascripts/"]
308end
309```
310
311#### JavaScript State
312
313Some 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.
314
315To manage state, you have a couple options:
316
317- Make a custom renderer with `#before_render` / `#after_render` hooks as [described below](#custom-server-renderer)
318- Use `per_request_react_rails_prerenderer` to manage state for a whole controller action.
319
320To check out a renderer for the duration of a controller action, call the `per_request_react_rails_prerenderer` helper in the controller class:
321
322```ruby
323class PagesController < ApplicationController
324 # Use the same React server renderer for the entire request:
325 per_request_react_rails_prerenderer
326end
327```
328
329Then, you can access the ExecJS context directly with `react_rails_prerenderer.context`:
330
331```ruby
332def show
333 react_rails_prerenderer # => #<React::ServerRendering::BundleRenderer>
334 react_rails_prerenderer.context # => #<ExecJS::Context>
335
336 # Execute arbitrary JavaScript code
337 # `self` is the global context
338 react_rails_prerenderer.context.exec("self.Store.setup()")
339 render :show
340 react_rails_prerenderer.context.exec("self.Store.teardown()")
341end
342```
343
344`react_rails_prerenderer` may also be accessed in before- or after-actions.
345
346#### Custom Server Renderer
347
348`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:
349
350- `#initialize(options={})`, which accepts the hash from `config.react.server_renderer_options`
351- `#render(component_name, props, prerender_options)` to return a string of HTML
352
353`react-rails` provides two renderer classes: `React::ServerRendering::ExecJSRenderer` and `React::ServerRendering::BundleRenderer`.
354
355`ExecJSRenderer` offers two other points for extension:
356
357- `#before_render(component_name, props, prerender_options)` to return a string of JavaScript to execute _before_ calling `React.render`
358- `#after_render(component_name, props, prerender_options)` to return a string of JavaScript to execute _after_ calling `React.render`
359
360Any subclass of `ExecJSRenderer` may use those hooks (for example, `BundleRenderer` uses them to handle `console.*` on the server).
361
362## Controller Actions
363
364Components can also be server-rendered directly from a controller action with the custom `component` renderer. For example:
365
366```ruby
367class TodoController < ApplicationController
368 def index
369 @todos = Todo.all
370 render component: 'TodoList', props: { todos: @todos }, tag: 'span', class: 'todo'
371 end
372end
373```
374
375You 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}`.
376
377Prerendering is set to `true` by default, but can be turned off with `prerender: false`.
378
379## Component Generator
380
381You can generate a new component file with:
382
383```sh
384rails g react:component ComponentName prop1:type prop2:type ...
385```
386
387For example,
388
389```sh
390rails g react:component Post title:string published:bool published_by:instanceOf{Person}
391```
392
393would generate:
394
395```js
396var Post = createReactClass({
397 propTypes: {
398 title: PropTypes.string,
399 published: PropTypes.bool,
400 publishedBy: PropTypes.instanceOf(Person)
401 },
402
403 render: function() {
404 return (
405 <div>
406 <div>Title: {this.props.title}</div>
407 <div>Published: {this.props.published}</div>
408 <div>Published By: {this.props.publishedBy}</div>
409 </div>
410 );
411 }
412};
413```
414
415The generator also accepts options:
416
417- `--es6`: use `class ComponentName extends React.Component`
418- `--coffee`: use CoffeeScript
419
420Accepted PropTypes are:
421
422- Plain types: `any`, `array`, `bool`, `element`, `func`, `number`, `object`, `node`, `shape`, `string`
423- `instanceOf` takes an optional class name in the form of `instanceOf{className}`.
424- `oneOf` behaves like an enum, and takes an optional list of strings in the form of `'name:oneOf{one,two,three}'`.
425- `oneOfType` takes an optional list of react and custom types in the form of `'model:oneOfType{string,number,OtherType}'`.
426
427Note that the arguments for `oneOf` and `oneOfType` must be enclosed in single quotes
428 to prevent your terminal from expanding them into an argument list.
429
430#### Use with JBuilder
431
432If you use Jbuilder to pass a JSON string to `react_component`, make sure your JSON is a stringified hash,
433not an array. This is not the Rails default -- you should add the root node yourself. For example:
434
435```ruby
436# BAD: returns a stringified array
437json.array!(@messages) do |message|
438 json.extract! message, :id, :name
439 json.url message_url(message, format: :json)
440end
441
442# GOOD: returns a stringified hash
443json.messages(@messages) do |message|
444 json.extract! message, :id, :name
445 json.url message_url(message, format: :json)
446end
447```
448
449### Camelize Props
450
451You can configure `camelize_props` option:
452
453```ruby
454MyApp::Application.configure do
455 config.react.camelize_props = true # default false
456end
457```
458
459Now, Ruby hashes given to `react_component(...)` as props will have their keys transformed from _underscore_- to _camel_-case, for example:
460
461```ruby
462{ all_todos: @todos, current_status: @status }
463# becomes:
464{ "allTodos" => @todos, "currentStatus" => @status }
465```
466
467You can also specify this option in `react_component`:
468
469```erb
470<%= react_component('HelloMessage', {name: 'John'}, {camelize_props: true}) %>
471```
472
473## Related Projects
474
475- [react\_on\_rails Gem](https://github.com/shakacode/react_on_rails): Integration of React with Rails utilizing Webpack, Babel, React, Redux, React-Router.
476- [Ruby Hyperloop](http://ruby-hyperloop.org/): Use Ruby to build reactive user interfaces with React.
477- [react-rails-hot-loader](https://github.com/rmosolgo/react-rails-hot-loader) is a simple live-reloader for `react-rails`.
478- [react-rails-benchmark_renderer](https://github.com/pboling/react-rails-benchmark_renderer) adds performance instrumentation to server rendering.
479- [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`.
480
481## Development
482
483- Run tests with `rake test` or `appraisal rake test`
484 - Integration tests run in Headless Chrome which is included in Chrome (59+ linux,OSX | 60+ Windows)
485 - ChromeDriver is included with `chromedriver-helper` gem so no need to manually install that 👍
486- Update React assets with `rake react:update`
487- Update the UJS with `rake ujs:update`
488- Releases:
489 - To release a new RubyGems version:
490 - Increment the version in `lib/react/rails/version.rb`
491 - Add an entry to `VERSIONS.md`
492 - Update the changelog (find recent changes on GitHub by listing commits or showing closed PRs)
493 - Commit changes & push to master
494 - `bundle exec rake release`: pushes a tag to GitHub, builds a `.gem`, and pushes to RubyGems
495 - To release a new NPM version:
496 - Update the version in `react_ujs/package.json`
497 - Commit & push to master
498 - `bundle exec rake ujs:publish` (runs `npm publish`)