How to use React in your Rails projects
Introduction
React is often used as the V in MVC and since it makes no assumptions about the rest of your technology stack and it’s easy to try it out on a small feature in an existing project. And besides that, it’s not so many new concepts to learn compared to AngularJS and Ember.
React uses a virtual DOM diff implementation to achieve very high performance. It’s also possible to do the rendering on the server. If you want to learn more about the virtual DOM in React you should take a look at The Secrets of React’s Virtual DOM (FutureJS 2014) by Pete Hunt.
In this tutorial I’m going to show you how to use React in Rails. It’s heavily based on the original tutorial for React but I have added Rails specific parts to it.
It’s all about components
In React, components are the central building blocks of your application. Components are self-contained, modular, dynamic representations of HTML in your application. Components are often children of other React components. We will illustrate how that works later in this tutorial.
Each React component has two types of inputs. The first one is properties (called props
) and they are immutable.
The second input is state
which is mutable. When we change the state, React will automatically re-render the component
so we can see the changes in the UI. All React components must implement a render method, which returns the HTML output.
This is a very simple React component:
/** @jsx React.DOM */
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.renderComponent(<HelloMessage name="Richard" />, mountNode);
Here you can see the render method that takes input data and returns what to display.
This example uses an XML-like syntax called JSX.
Input data that is passed into the component can be accessed by render()
via this.props
.
JSX is optional in React so if you want to, you can implement the returning HTML in pure Javascript.
Attention: Notice the comment on top of the file. It’s required to make the compilation from JSX to plain Javascript to work.
The JSX compiler will produce the following Javascript:
/** @jsx React.DOM */
var HelloMessage = React.createClass({displayName: 'HelloMessage',
render: function() {
return React.DOM.div(null, "Hello ", this.props.name);
}
});
React.renderComponent(HelloMessage({name: "Richard"}), mountNode);
What we are going to build
As I wrote earlier, this tutorial is going to be heavily based on the tutorial you can find on the React home page.
We’ll build a simple comments box that you can drop into a blog, a basic version of the comments functionality offered by Disqus or Facebook comments.
We’ll provide:
-
A view of all the comments
-
A form to submit a comment
-
A JSON API built with Rails to list and create new comments.
Setup the Rails API
The first thing we need to do is to setup the Rails backend so our React frontend can create and list comments from the server.
Start by creating a new Rails project:
rails new react-demo
First we need to add the following to our Gemfile:
gem 'active_model_serializers'
gem 'ffaker'
Run bundle install
to install your gems.
The active_model_serializers gem encapsulates the JSON serialization of ActiveRecord objects. A serializer will automatically be created when we use the Rails generator to generate the comment resource.
The ffaker gem will be used to create sample data for our application.
Next, we need to create our resource:
rails g resource comment comment:text author:string
Because we are using the active_model_serializers gem, a serializer will automatically be generated for us:
# app/serializers/comment_serializer.rb
class CommentSerializer < ActiveModel::Serializer
attributes :id, :comment, :author
end
Now we can create the comments api controller. The actions here are pretty standard:
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
respond_to :json
def index
respond_with Comment.all
end
def create
respond_with Comment.create(comment_params)
end
private
def comment_params
params.require(:comment).permit(:author, :comment)
end
end
We can now create some sample data to test our new Rails API. Create a new rake task:
# lib/tasks/populate.rake
namespace :db do
task populate: :environment do
Comment.destroy_all
10.times do
Comment.create(
author: Faker::Name.first_name + " " + Faker::Name.last_name,
comment: Faker::HipsterIpsum.words(10).join(' ')
)
end
end
end
Now run:
rake db:migrate
rake db:populate
rails server
Now you can try out your comments API:
http://localhost:3000/comments.json and see the JSON output for all comments.
When you look at the json output you’ll see that it has a root element for comments. To get rid of it we can create an initializer:
# config/initializers/active_model_serializer.rb
ActiveModel::Serializer.root = false
ActiveModel::ArraySerializer.root = false
Restart your server and the root elements will now be gone.
Now we have a fully functional Rails API that we can use.
Setup React in Rails
For this tutorial we are going to use the react-rails gem which installs the required Javascript files needed.
Open your Gemfile and add the following:
gem 'react-rails'
Run bundle install
Then we need to add react to application.js
:
// app/assets/javascripts/application.js
//= require react
Make sure to require react after turbolinks or weird things might happen.
You also need to configure variants to use for different environments.
There are two variants available. :development
gives you the unminified version of React.
This provides extra debugging and error prevention. :production
gives you the minified version of
React which strips out comments and helpful warnings.
# config/environments/development.rb
Rails.application.configure do
config.react.variant = :development
end
# config/environments/production.rb
Rails.application.configure do
config.react.variant = :production
end
Next, we need to create a home controller which we will wire to the root url:
rails g controller home index
Then open up your routes.rb
file and add this:
# config/routes.rb
root 'home#index'
After this we can replace the index view content with the following content:
# app/views/home/index.html.erb
<div id="comments"></div>
The comments div will be used as a starting point where we will render the React stuff.
OK, now we should have everything setup to start using React. To prove it we can add
this Hello World example in a file called comments.js.jsx
(you should remove the comments.js file that already exists):
# app/assets/javascripts/comments.js.jsx
/** @jsx React.DOM */
var HelloWorld = React.createClass({
render: function() {
return (
<div className='HelloWorld'>
Hello, world!
</div>
);
}
});
var ready = function () {
React.renderComponent(
<HelloWorld />,
document.getElementById('comments')
);
};
$(document).ready(ready);
When you visit http://localhost:3000 you should now see Hello, world!
Implementing the comment component
Now is the time to start implementing the real React components for this tutorial. As we said earlier React is all about modular and composable component.
The following component structure will be used in this tutorial:
-
- CommentBox
-
- CommentList
-
- Comment
-
-
- CommentForm
-
We’re going to implement the components from inside out so the first one will be the Comment
component.
The Comment
component will be responsible for rendering a single comment with an author and comment text property:
Replace the Hello World example in your comments.js.jsx
with this:
# app/assets/javascripts/comments.js.jsx
/** @jsx React.DOM */
var Comment = React.createClass({
render: function () {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.comment}
</div>
);
}
});
var ready = function () {
React.renderComponent(
<Comment author="Richard" comment="This is a comment "/>,
document.getElementById('comments')
);
};
$(document).ready(ready);
So the only thing that’s different here compared to the Hello World component is
that we’re passing in hard coded properties that we use in the render method.
By surrounding a JavaScript expression in braces inside JSX , you can drop text or React components into the tree.
We access named attributes passed to the component as keys on this.props
.
You can now refresh the page in your browser and you should see the comment.
Implementing the CommentList component
The next component we need to add is the CommentList
component which will
be responsible for rendering a list of comments:
/** @jsx React.DOM */
var Comment = // Removed to save space
var CommentList = React.createClass({
render: function () {
var commentNodes = this.props.comments.map(function (comment, index) {
return (
<Comment author={comment.author} comment={comment.comment} key={index} />
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
var ready = function () {
var fakeComments = [
{ author:"Richard", comment:"This is a comment" },
{ author:"Nils", comment:"This is another comment" }
];
React.renderComponent(
<CommentList comments={fakeComments} />,
document.getElementById('comments')
);
};
$(document).ready(ready);
OK, let’s start from the bottom and take a look at the React.renderComponent
call.
We’re now passing in an array with fake comments to the CommentList
component.
In the render method in CommentList
we’re using the map function to iterate thru
the array of comments and returning a new array with new Comment
component instances.
Then we just render the CommentList
and adds the list with comments.
Refresh the page in the browser and you should now see two hard coded comments.
Implement the CommentBox component
Now is the time to add the top level CommentBox
component. The CommentBox
will
be responsible for displaying the CommentList
and the CommentForm
(we will implement it later) on the page. It’s also
this component that will talk to our backend:
/** @jsx React.DOM */
var Comment = // Removed to save space
var CommentList = // Removed to save space
var CommentBox = React.createClass({
getInitialState: function () {
return {comments: []};
},
componentDidMount: function () {
this.loadCommentsFromServer();
},
loadCommentsFromServer: function () {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function (comments) {
this.setState({comments: comments});
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList comments={this.state.comments} />
</div>
);
}
});
var ready = function () {
React.renderComponent(
<CommentBox url="/comments.json" />,
document.getElementById('comments')
);
};
$(document).ready(ready);
As you can see, the CommentBox
is a little bit more complicated and contains
more React specific code that we need to explain.
So far, each component has rendered itself once based on its props. To implement interactions,
we introduce mutable state to the component. this.state
is private to the component and can be
changed by calling this.setState()
. When the state is updated, the component re-renders itself.
The getInitialState
method is a special method that executes exactly once during the
lifecycle of the component and sets up the initial state of the component. In the CommentBox
we
set an empty list with comments.
The next method is componentDidMount
which is automatically called by React when the
component is rendered. In this example we only execute the loadCommentsFromServer
.
This method uses plain old jQuery to fetch comments from our API.
When we get a successful response from the server, we change the state of the old comments array with
a new one from the server by calling this.setState({comments: comments})
The UI will automatically updates itself.
The comment form component
Now it’s time to build the form. Our CommentForm
component should ask the user
for their name and comment text and send a request to the server to save the comment.
When the user submits the form, we should clear it, submit a request to the server, and refresh the list of comments:
var Comment = // Removed to save space
var CommentList = // Removed to save space
var CommentBox = React.createClass({
...
handleCommentSubmit: function(comment) {
var comments = this.state.comments;
var newComments = comments.concat([comment]);
this.setState({comments: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: {"comment": comment},
success: function(data) {
this.loadCommentsFromServer();
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList comments={this.state.comments} />
<CommentForm onCommentSubmit={this.handleCommentSubmit}/>
</div>
);
}
});
var CommentForm = React.createClass({
handleSubmit: function() {
var author = this.refs.author.getDOMNode().value.trim();
var comment = this.refs.comment.getDOMNode().value.trim();
this.props.onCommentSubmit({author: author, comment: comment});
this.refs.author.getDOMNode().value = '';
this.refs.comment.getDOMNode().value = '';
return false;
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" ref="author" />
<input type="text" placeholder="Say something..." ref="comment" />
<input type="submit" value="Post" />
</form>
);
}
});
React attaches event handlers
to components using a camelCase naming convention.
We attach an onSubmit
handler to the form that handles the submit and clears the form afterwards.
We always return false from the event handler to prevent the browser’s default action of submitting the form. Also notice
the ref
attributes used in the JSX code for the inputs. We use the ref
attribute to assign a name to a child component
and this.refs
to reference the component. We can call getDOMNode()
on a component to get the native browser DOM element.
Also notice that we have changed CommentBox
component. The render method now contains
the CommentForm
component and when you click submit, the handleCommentSubmit method
will be executed. This method posts the new comment to the server and updates the state to reflect
the changes.
The complete comments.js.jsx
can be found here: https://gist.github.com/ricn/678fff7f0f7749e15080
Congrats!
You have just built a comment box in a few simple steps using Rails as the backend.
If you look for more information on React, check out the official docs.