How to Build a React App that Works with a Rails 5.1 API

React + Ruby on Rails = 🔥

React has taken the frontend development world by storm. It’s an excellent JavaScript library for building user interfaces. And it’s great in combination with Ruby on Rails. You can use Rails on the back end with React on the front end in various ways.

In this hands-on tutorial, we’re going to build a React app that works with a Rails 5.1 API.

You can watch a video version of this tutorial here.

Watch a video version of this tutorial

To follow this tutorial, you need to be comfortable with Rails and know the basics of React.

[affiliate-section title=”Recommended Courses”][affiliate-card title=”The Best Way to Learn React for Beginners” affiliatename=”Wes Bos” text=”A step-by-step training course to get you building real world React.js + Firebase apps and website components in a couple of afternoons. Use coupon code ‘SITEPOINT’ at checkout to get 25% off.” url=”https://ReactForBeginners.com/friend/SITEPOINT” imageurl=”https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/07/1501203893wesbos.jpg”][/affiliate-section]

If you don’t use Rails, you can also build the API in the language or framework of your choice, and just use this tutorial for the React part.

The tutorial covers stateless functional components, class-based components, using Create React App, use of axios for making API calls, immutability-helper and more.

What We’re Going to Build

We’re going to build an idea board as a single page app (SPA), which displays ideas in the form of square tiles.

You can add new ideas, edit them and delete them. Ideas get auto-saved when the user focuses out of the editing form.

A demo of the Idea board app

At the end of this tutorial, we’ll have a functional CRUD app, to which we can add some enhancements, such as animations, sorting and search in a future tutorial.

You can see the full code for the app on GitHub:

Ideaboard Rails API

Ideaboard React frontend

Setting up the Rails API

Let’s get started by building the Rails API. We’ll use the in-built feature of Rails for building API-only apps.

Make sure you have version 5.1 or higher of the Rails gem installed.

gem install rails -v 5.1.3

At the time of writing this tutorial, 5.1.3 is the latest stable release, so that’s what we’ll use.

Then generate a new Rails API app with the --api flag.

rails new --api ideaboard-api
cd ideaboard-api

Next, let’s create the data model. We only need one data model for ideas with two fields — a title and a body, both of type string.

Let’s generate and run the migration:

rails generate model Idea title:string body:string

rails db:migrate

Now that we’ve created an ideas table in our database, let’s seed it with some records so that we have some ideas to display.

In the db/seeds.rb file, add the following code:

ideas = Idea.create(
  [
    {
      title: "A new cake recipe",
      body: "Made of chocolate"
    },
    {
      title: "A twitter client idea",
      body: "Only for replying to mentions and DMs"
    },
    {
      title: "A novel set in Italy",
      body: "A mafia crime drama starring Berlusconi"
    },
    {
      title: "Card game design",
      body: "Like Uno but involves drinking"
    }
  ])

Feel free to add your own ideas.

Then run:

rails db:seed

Next, let’s create an IdeasController with an index action in app/controllers/api/v1/ideas_controller.rb:

module Api::V1
  class IdeasController < ApplicationController
    def index
      @ideas = Idea.all
      render json: @ideas
    end
  end
end

Note that the controller is under app/controllers/api/v1 because we’re versioning our API. This is a good practice to avoid breaking changes and provide some backwards compatibility with our API.

Then add ideas as a resource in config/routes.rb:

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :ideas  
    end
  end
end

Alright, now let’s test our first API endpoint!

First, let’s start the Rails API server on port 3001:

rails s -p 3001

Then, let’s test our endpoint for getting all ideas with curl:

curl -G http://localhost:3001/api/v1/ideas

And that prints all our ideas in JSON format:

[{"id":18,"title":"Card game design","body":"Like Uno but involves drinking","created_at":"2017-09-05T15:42:36.217Z","updated_at":"2017-09-05T15:42:36.217Z"},{"id":17,"title":"A novel set in Italy","body":"A mafia crime drama starring Berlusconi","created_at":"2017-09-05T15:42:36.213Z","updated_at":"2017-09-05T15:42:36.213Z"},{"id":16,"title":"A twitter client idea","body":"Only for replying to mentions and DMs","created_at":"2017-09-05T15:42:36.209Z","updated_at":"2017-09-05T15:42:36.209Z"},{"id":15,"title":"A new cake recipe","body":"Made of chocolate","created_at":"2017-09-05T15:42:36.205Z","updated_at":"2017-09-05T15:42:36.205Z"}]

We can also test the endpoint in a browser by going to http://localhost:3001/api/v1/ideas.

Testing our API endpoint in a browser

Setting up Our Front-end App Using Create React App

Now that we have a basic API, let’s set up our front-end React app using Create React App. Create React App is a project by Facebook that helps you get started with a React app quickly without any configuration.

First, make sure you have Node.js and npm installed. You can download the installer from the Node.js website. Then install Create React App by running:

npm install -g create-react-app

Then, make sure you’re outside the Rails directory and run the following command:

create-react-app ideaboard

That will generate a React app called ideaboard, which we’ll now use to talk to our Rails API.

Let’s run the React app:

cd ideaboard
npm start

This will open it on http://localhost:3000.

Homepage of a new app generated by Create React App

The app has a default page with a React component called App that displays the React logo and a welcome message.

The content on the page is rendered through a React component in the src/App.js file:

import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App

Our First React Component

Our next step is to edit this file to use the API we just created and list all the ideas on the page.

Let’s start off by replacing the Welcome message with an h1 tag with the title of our app ‘Idea Board’.

Let’s also add a new component called IdeasContainer. We need to import it and add it to the render function:

import React, { Component } from 'react'
import './App.css'
import IdeasContainer from './components/IdeasContainer'

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <h1>Idea Board</h1>
        </div>
        <IdeasContainer />
      </div>
    );
  }
}

export default App

Let’s create this IdeasContainer component in a new file in src/IdeasContainer.js under a src/components directory.

import React, { Component } from 'react'

class IdeasContainer extends Component {
  render() {
    return (
      <div>
        Ideas
      </div>
    )
  }
}

export default IdeasContainer

Let’s also change the styles in App.css to have a white header and black text, and also remove styles we don’t need:

.App-header {
  text-align: center;
  height: 150px;
  padding: 20px;
}

.App-intro {
  font-size: large;
}

Skeleton Idea board app

This component needs to talk to our Rails API endpoint for getting all ideas and display them.

Fetching API Data with axios

We’ll make an Ajax call to the API in the componentDidMount() lifecycle method of the IdeasContainer component and store the ideas in the component state.

Let’s start by initializing the state in the constructor with ideas as an empty array:

constructor(props) {
  super(props)
  this.state = {
    ideas: []
  }
}

And then we’ll update the state in componentDidMount().

Let’s use the axios library for making the API calls. You can also use fetch or jQuery if you prefer those.

Install axios with npm:

npm install axios --save

Then import it in IdeasContainer:

import axios from 'axios'

And use it in componentDidMount():

componentDidMount() {
  axios.get('http://localhost:3001/api/v1/ideas.json')
  .then(response => {
    console.log(response)
    this.setState({ideas: response.data})
  })
  .catch(error => console.log(error))
}

Now if we refresh the page … it won’t work!

No Access-Control-Allow-Origin header present

We’ll get a “No Access-Control-Allow-Origin header present” error, because our API is on a different port and we haven’t enabled Cross Origin Resource Sharing (CORS).

Enabling Cross Origin Resource Sharing (CORS)

So let’s first enable CORS using the rack-cors gem in our Rails app.

Add the gem to the Gemfile:

gem 'rack-cors', :require => 'rack/cors'

Install it:

bundle install

Then add the middleware configuration to config/application.rb file:

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3000'
    resource '*', :headers => :any, :methods => [:get, :post, :put, :delete, :options]
  end
end

We restrict the origins to our front-end app at http://localhost:3000 and allow access to the standard REST API endpoint methods for all resources.

Now we need to restart the Rails server, and if we refresh the browser, we’ll no longer get the CORS error.

The page will load fine and we can see the response data logged in the console.

Ideas JSON response from API logged to the console

So now that we know we’re able to fetch ideas from our API, let’s use them in our React component.

We can change the render function to iterate through the list ideas from the state and display each of them:

render() {
  return (
    <div>
      {this.state.ideas.map((idea) => {
        return(
          <div className="tile" key={idea.id} >
            <h4>{idea.title}</h4>
            <p>{idea.body}</p>
          </div>
        )       
      })}
    </div>
  );
}

That will display all the ideas on the page now.

List of ideas displayed by component

Note the key attribute on the tile div.

We need to include it when creating lists of elements. Keys help React identify which items have changed, are added, or are removed.

Now let’s add some styling in App.css to make each idea look like a tile:

.tile {
  height: 150px;
  width: 150px;
  margin: 10px;
  background: lightyellow;
  float: left;
  font-size: 11px;
  text-align: left;
}

We set the height, width, background color and make the tiles float left.

Styled idea tiles

Stateless functional components

Before we proceed, let’s refactor our code so far and move the JSX for the idea tiles into a separate component called Idea.

import React from 'react'

const Idea = ({idea}) =>
  <div className="tile" key={idea.id}>
    <h4>{idea.title}</h4>
    <p>{idea.body}</p>
  </div>

export default Idea

This is a stateless functional component (or as some call it, a “dumb” component), which means that it doesn’t handle any state. It’s a pure function that accepts some data and returns JSX.

Then inside the map function in IdeasContainer, we can return the new Idea component:

{this.state.ideas.map((idea) => {
  return (<Idea idea={idea} key={idea.id} />)
})}

Don’t forget to import Idea as well:

import Idea from './Idea'

Great, so that’s the first part of our app complete. We have an API with an endpoint for getting ideas and a React app for displaying them as tiles on a board!

Adding a new record

Next, we’ll add a way to create new ideas.

Let’s start by adding a button to add a new idea.

Inside the render function in IdeasContainer, add:

<button className="newIdeaButton">
  New Idea
</button>

And let’s add some styling for it in App.css:

.newIdeaButton {
  background: darkblue;
  color: white;
  border: none;
  font-size: 18px;
  cursor: pointer;
  margin-right: 10px;
  margin-left: 10px;
  padding:10px;
}

New Idea button

Now when we click the button, we want another tile to appear with a form to edit the idea.

Once we edit the form, we want to submit it to our API to create a new idea.

Continue reading %How to Build a React App that Works with a Rails 5.1 API%


Source: Sitepoint