This post details setting up a rails application for development with react-rails and npm-modules.

I have lately been working on an React application with a Rails backend. There was a need to implement drag & drop functionality, for which I wanted to use ReactDnD, a module that depends upon using commonJS style ‘require’ calls.

After an aborted attempt to create an asset gem for ReactDnD, I settled upon using Browserify to make npm packages available as assets.

Install Node.js

First, naturally, we will need Node!

$ apt-get install nodejs

If you’re not using an apt based distro, hit up the node site for a tar ball.

This should install npm:

$ npm -v
1.4.28

Generate the Rails app

If you’re not adding to an extant app, go ahead and generate a new Rails application now:

$ rails new react-sample

Add Gemfile dependencies

The first gem we’ll want is react-rails:

gem 'react-rails', '~> 1.0.0'

react-rails provides React in the asset pipeline, JSX transformation, view helpers & React UJS, serverside pre-rendering & a component generator task.

Whilst this setup doesn’t require all of react-rails’ functionality, it is nevertheless useful.

If you have no need of commonJS style require calls, you can stop here as react-rails provides all you need to get started developing React interfaces for Rails applications.

Otherwise, add browserify-rails to your gemfile:

gem 'browserify-rails', '~> 1.0.1'

Create package.json

npm provides a handy utility for generating a package.json file, but you can do this by hand, if you like. See my example at the end of this section for reference!

To use the generator:

$ npm init

and follow the prompts. We aren’t concerned with an entry point, and you may not necessarily care about the other fields the initializer adds to the package.json file. Check out this sick interactive guide to the various parts of a package.json file!

Here’s my package file:

{
  "name": "react-sample",
  "devDependencies": {
    "browserify": "^10.2.4",
    "browserify-incremental": "^3.0.1",
    "reactify": "^1.1.1",
    "react": "^0.13.3"
  }
  "engines": {
    "node": ">= 0.10"
  }
}

Browserify gives us require() in the browser by parsing modules containing these calls and bundling them for the client-side.

Browserify Incremental allows for incremental rebuilds of our modules - meaning only changed files will be parsed.

Reactify is a Browserify plugin which provides a transformer for JSX. We need to farm this out to Browserify in order for the require() calls to be appropriately handled.

React we know about, of course. We add it as a dependency here, as we will need to be able to require() it for third party modules to work with it. Though react-rails also provides the library, we will use this version.

In this case, we specify the modules as devDependencies - Browserify will prepare static files for us - but you would want to specify them as dependencies if you need the modules on the server.

If you’re using git, add the node_modules directory to your .gitignore:

$ echo 'node_modules/' >> .gitignore

And go ahead and run:

$ npm install

to install these modules.

Config items

Add this to config/application.rb to instruct Browserify to transform .jsx files:

config.browserify_rails.commandline_options = "--transform reactify --extension=\".jsx\""

Add these to config/environments/development.rb and config/environments/production.rb:

#development.rb:
config.react.variant = :development

#production.rb:
config.react.variant = :production

The development react variant will give us more debugging information, which we wont want in the production environment.

Javascript

It is my preference to store React components under app/assets/javascripts/components, and index these in a components.js file in the javascripts asset directory:

$ cd app/assets/javascripts
$ touch components.js && mkdir components

Next, we must make React available to the clientside javascript.

In a typical react-rails application we would use a sprockets require directive. We cannot do that here, as we will need any React plugins that depend on require()ing React to be able to do so. Instead, we will include react_ujs in the normal manner, and require() React and its add ons.

//app/assets/javascripts/components.js

//= require_self
//= require react_ujs

React    = require('react');

//add other modules as needed, eg:
//ReactDnD = require('react-dnd');
// ...

and use an ordinary sprocket require to include this file globally:

//app/assets/javascripts/application.js

//..
//= require components
//

Components

The process for creating React components is more or less the same as with react-rails, but there are a couple of gotchas.

Firstly, since we are using reactify to handle jsx transformation, the component files will not need to be named .js.jsx, as sprockets will not be performing the transformation. Simply .jsx will be fine.

Additionally, we will need to export from the file so our components can be required, eg:

module.exports = Widgets;

and for each component require the .jsx file:

// for Widgets.jsx, eg

Widgets = require ('./components/Widgets.jsx');

So a component might look something like:

var Widgets = React.createClass({
  render: function() {
    return (
      <div className="widgets">
        <FooWidgets/>
      </div>
    );
  }
});

module.exports = Widgets;

and be required with:

// app/assets/javascripts/components.js
Widgets = require('./components/Widgets.jsx');

Rendering React components

Finally, we can call the react_rails view helper to render our components:

<%= react_component 'Widgets' %>

We can also use this view helper to pass props into the component, as per the react-rails documentation.

Update: Gotcha for working with Jest

In this setup, React is required once (in components.js) for all the components. Jest tests each component in isolation, which means with this setup React is undefined in a testing context.

This is easy to work around, though. In your tests, when requiring react/addons, define React as a global:

global.React = require('react/addons');

and it will be available to both the test and component.