# Getting up and running with React.js and Rails
I'm sure there are arleady plenty of tutorials out there on using React with Rails, but I'm posting this tutorial in response to my last blog post about using Vue.js and Rails. I'm going to follow the same steps as before and build the same application. My thoughts on a comparison of the two frameworks will come in a future blog post.
# What are we Building?
We will be building an Employee management tool that will demonstrate all the basic CRUD actions. The goal is to use React.js to perform all the actions from a single page. We will be able to list current employees, hire new employees, edit employee information, promote and demote employees, and fire an employee (in case they get ornery). This tutorial will require basic knowledge of Rails.
# Loading in React.js
I've decided to take advantage of an existing ruby gem called react-rails. Installing was as simple as adding the gem to the Gemfile and running rails g react:install
which generates the asset structure. More details about react-rails can be found on it's readme.
# Initial Setup
We will create an Empoyee model and controller along with a couple javascript files for the react components. You will have to set your routes appropriatley to handle all the actions that we will go through.
We will also need to create a migration and run it. We will create an employees table with name as a string, email as a string, and manager as a boolean. The manager column basically indicates manager status in which we will create a button that can toggle the status of said employee.
# migration
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :name
t.string :email
t.boolean :manager
t.timestamps null: false
end
end
end
# Listing Employees
The first step will be to seed in some data into the employees table and create the initial view to display the employees in a table. Fairly simple and straight forward. The react-rails gem allows us to create a controller action that will render the react component to list all of the employees. This eliminates the need for an html or erb template.
# app/controllers/employees_controller.rb
class EmployeesController < ApplicationController
def index
@employees = Employee.all
render component: 'Employees', props: { employees: @employees }
end
end
// app/assets/javascripts/components/employees.js.jsx
var Employees = React.createClass({
render: function() {
employees = this.props.employees.map( function(employee) {
return (
<tr key={employee.id}>
<td>{employee.name}</td>
<td>{employee.email}</td>
<td>{employee.manager ? '✔' : ''}</td>
</tr>
);
});
return (
<div>
<h1>Employees</h1>
<div id="employees">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Manager</th>
</tr>
</thead>
<tbody>
{employees}
</tbody>
</table>
</div>
</div>
);
}
});
# Moving the Row for an Employee into a Component
To perform all the actions that are required for the application, it will serve us better to move the employee into a React Component. This can be accomplished by passing the employee object as a prop to the component. We can create another within the components directory to create the Employee component.
// app/assets/javascripts/components/employee.js.jsx
var Employee = React.createClass({
getInitialState() {
return {
employee: this.props.employee
}
},
render() {
return (
<tr>
<td>{this.state.employee.name}</td>
<td>{this.state.employee.email}</td>
<td>{this.state.employee.manager ? '✔' : ''}</td>
</tr>
);
}
});
The employees component will also be updated to render the Employee component appropriately.
// app/assets/javascripts/components/employees.js.jsx
// ...
render () {
employees = this.state.employees.map( function(employee) {
return (
<Employee employee={employee} key={employee.id} />
);
});
// ...
# Hiring an Employee
Here's where it starts to get fun. In order to hire an employee we will need to add a row to the table that will include fields to set Name, Email, and Manager status. The data for the main React instance will need an object for a new employee as well as an errors object to handle validation errors.
// app/assets/javascripts/components/employees.js.jsx
// ...
getInitialState() {
return {
employees: this.props.employees,
employee: {
name: '',
email: '',
manager: false
},
errors: {}
}
},
// ...
// app/assets/javascripts/components/employees.js.jsx
//... inside render() {
<tbody>
{employees}
<tr>
<td>
<input type="text" onChange={this.handleNameChange} /><br />
<span style={{color: 'red'}}>{this.state.errors.name}</span>
</td>
<td>
<input type="text" onChange={this.handleEmailChange} /><br />
<span style={{color: 'red'}}>{this.state.errors.email}</span>
</td>
<td><input type="checkbox" onChange={this.handleManagerChange} /></td>
<td><button onClick={this.handleHireEmployee}>Hire</button></td>
</tr>
</tbody>
// ...
The controller for the create action will respond with the created employee or the errors if any validations are not met.
# app/controllers/employees_controller.rb
# ...
def create
@employee = Employee.new(employee_params)
respond_to do |format|
format.json do
if @employee.save
render :json => @employee
else
render :json => { :errors => @employee.errors.messages }, :status => 422
end
end
end
end
private
def employee_params
params.require(:employee).permit(:name, :email, :manager)
end
end
We will also add a handleHireEmployee
method to the React component, which will be called when the "Hire" button is clicked as shown in the template. This method makes an ajax call to the create action for employees using the values set for the employee object.
// app/assets/javascripts/componenets/employees.js.jsx
// ...
handleHireEmployee() {
var that = this;
$.ajax({
method: 'POST',
data: {
employee: that.state.employee,
},
url: '/employees.json',
success: function(res) {
var newEmployeeList = that.state.employees;
newEmployeeList.push(res);
that.setState({
employees: newEmployeeList,
employee: {
name: '',
email: '',
manager: false
},
errors: {}
});
},
error: function(res) {
that.setState({errors: res.responseJSON.errors})
}
});
},
// ...
We will also need methods to persist the state of the input values for name, email, and manager. This will ensure that these values persist in the state until the employee values are successfully saved.
// app/assets/javascripts/componenets/employees.js.jsx
// ...
handleNameChange(e) {
var newEmployee = this.state.employee;
newEmployee.name = e.target.value;
this.setState({employee: newEmployee});
},
handleEmailChange(e) {
var newEmployee = this.state.employee;
newEmployee.email = e.target.value;
this.setState({employee: newEmployee});
},
handleManagerChange(e) {
var newEmployee = this.state.employee;
newEmployee.manager = e.target.value;
this.setState({employee: newEmployee});
},
// ...
# Editing an Employee
We will add an Edit button to each row which will toggle each column to an input field for that employee. Calling the update action in the controller will be very similar to the create action, except we are finding the employee then calling update instead of save. The update action will be called when the Save button is clicked when in edit mode. We will also add a Promote/Demote toggle which will toggle the manager status without having to edit the other fields. The javascript and html required to make this happen will go inside the React component.
# app/controllers/employees_controller.rb
# ...
def update
@employee = Employee.find(params[:id])
respond_to do |format|
format.json do
if @employee.update(employee_params)
render :json => @employee
else
render :json => { :errors => @employee.errors.messages }, :status => 422
end
end
end
end
# ...
// app/assets/javascripts/components/employee.js.jsx
var Employee = React.createClass({
getInitialState() {
return {
employee: this.props.employee,
editMode: false,
errors: {}
}
},
setEditMode() {
this.setState({editMode: true});
},
handleNameChange(e) {
var newEmployee = this.state.employee;
newEmployee.name = e.target.value;
this.setState({employee: newEmployee});
},
handleEmailChange(e) {
var newEmployee = this.state.employee;
newEmployee.email = e.target.value;
this.setState({employee: newEmployee});
},
handleManagerChange(e) {
var newEmployee = this.state.employee;
newEmployee.manager = e.target.value;
this.setState({employee: newEmployee});
},
toggleManagerStatus() {
var newEmployee = this.state.employee;
newEmployee.manager = !this.state.employee.manager;
this.setState({employee: newEmployee});
this.handleEmployeeUpdate();
},
handleEmployeeUpdate() {
var that = this;
$.ajax({
method: 'PUT',
data: {
employee: that.state.employee,
},
url: '/employees/' + that.state.employee.id + '.json',
success: function(res) {
that.setState({
errors: {},
employee: res,
editMode: false
});
},
error: function(res) {
that.setState({errors: res.responseJSON.errors});
}
});
},
render() {
if ( this.state.editMode ) {
markup = (
<tr>
<td>
<input
type="text"
value={this.state.employee.name}
onChange={this.handleNameChange} />
<span style={{color: 'red'}}>{this.state.errors.name}</span>
</td>
<td>
<input
type="text"
value={this.state.employee.email}
onChange={this.handleEmailChange} />
<br />
<span style={{color: 'red'}}>{this.state.errors.email}</span>
</td>
<td>
<input
type="checkbox"
value={this.state.employee.manager}
onChange={this.handleManagerChange} />
</td>
<td>
<button onClick={this.handleEmployeeUpdate}>Save</button>
</td>
</tr>
);
} else {
markup = (
<tr>
<td>{this.state.employee.name}</td>
<td>{this.state.employee.email}</td>
<td>{this.state.employee.manager ? '✔' : ''}</td>
<td>
<button onClick={this.setEditMode}>Edit</button>
<button onClick={this.toggleManagerStatus}>{this.state.employee.manager ? 'Demote' : 'Promote'}</button>
</td>
</tr>
);
}
return markup;
}
});
# Firing an Employee
Last but not least, we will need to be able to fire employees. The fire button will call handleEmployeeFire, which will in turn call the destroy action for the controller.
# app/controllers/employees_controller.rb
# ...
def destroy
Employee.find(params[:id]).destroy
respond_to do |format|
format.json { render :json => {}, :status => :no_content }
end
end
# ...
// app/assets/javascripts/components/employee.js.jsx
// Inside the employee component
handleEmployeeFire() {
var that = this;
$.ajax({
method: 'DELETE',
url: '/employees/' + that.state.employee.id + '.json',
success: function(res) {
that.props.onFireEmployee(that.state.employee);
}
})
},
// ...
// Button markup inside render() {
<button onClick={this.handleEmployeeFire} style={{color: 'red'}}>Fire</button>
// ...
As seen in the method above, we are calling the prop method onFireEmployee
on successful deleteion which is defined in the Employees component.
// app/assets/javascripts/components/employees.js.jsx
// ...
handleFireEmployee(employee) {
var employeeList = this.state.employees.filter(function(item) {
return employee.id !== item.id;
});
this.setState({employees: employeeList});
},
// ...
// Employee component render
employees = this.state.employees.map( function(employee) {
return (
<Employee employee={employee} key={employee.id} onFireEmployee={that.handleFireEmployee} />
);
});
// ...
And that about wraps it up. It's been a pleasure learning React, but I have to say that my knowledge of the library is very limited. I would appreciate any comments, and would love to get feedback from others who have worked with React.js and Rails. Check out the full source code on Github.