Part 3 of 3: Handling requests between React and Rails
Recap
In parts 1 & 2 of this series we covered:
All of the code for this series resides at: https://github.com/oddballio/rails-with-react
Introduction
A traditional Rails app has the following general life cycle when rendering a page:
- User visits a URL
 - An HTTP request is made to this URL
 - The path is identified in 
routes.rb, and calls the associated controller action - The controller action executes its logic
 - The controller action renders a view, passing any relevant return data to the view
 
In this tutorial we'll cover how to recreate this pattern with a React view layer that interacts with the Rails backend.
GET request
We'll represent the HTTP GET request process through the Posts.js component.  This component will call the posts_controller#index Rails action in order to render a list of posts.
Rails controller action
This will be a typical Rails controller action, with a few adjustments to make it behave like an API.
1. Create an api folder underneath app/controllers/
This namespacing of our controllers and routes mitigates against any potential collisions between a React route and a Rails route.
2. In config/initializers/inflections.rb implement an inflection rule to allow the api namespace to be referenced as a capitalized API module during routing
The Rails guide explains this as:
specifying any word that needs to maintain a non-standard capitalization
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
end
3. Create a app/controllers/api/posts_controller.rb with an index action
For simplicity, we'll simulate that the index action is returning a collection of Post records from the database through a stubbed out array of posts.
# app/controllers/api/posts_controller.rb
module API
  class PostsController < ApplicationController
    def index
      posts = ['Post 1', 'Post 2']
      render json: { posts: posts }
    end
  end
end
  
  
  Rails posts#index route
In keeping with our controller namespacing, we'll namespace all of our routes within an :api namespace.
# config/routes.rb
Rails.application.routes.draw do
  root 'pages#index'
  namespace :api, defaults: { format: 'json' } do
    resources :posts, only: :index
  end
  # IMPORTANT #
  # This `match` must be the *last* route in routes.rb
  match '*path', to: 'pages#index', via: :all
end
  
  
  Calling posts#index from React
Our Posts.js component will need to make an HTTP GET request to our new posts#index endpoint.  To do this, we will use the Axios HTTP client.
1. Install Axios
$ yarn add axios
2. Import Axios into Posts.js
// app/javascript/components/Posts.js
import axios from 'axios'
...
3. Call posts#index with Axios
We’ll make the Axios call within a componentDidMount() React lifecycle method. You can read more about lifecycle methods in this Guide to React Component Lifecycle Methods.
Note that the Rails route for the posts#index endpoint is /api/posts.
// app/javascript/components/Posts.js
...
class Posts extends React.Component {
  state = {
    posts: []
  };
  componentDidMount() {
    axios
      .get('/api/posts')
      .then(response => {
        this.setState({ posts: response.data.posts });
      })
  }
...
4. Display the posts returned from the posts#index call
// app/javascript/components/Posts.js
...
  renderAllPosts = () => {
    return(
      <ul>
        {this.state.posts.map(post => (
          <li key={post}>{post}</li>
        ))}
      </ul>
    )
  }
  render() {
    return (
      <div>
        {this.renderAllPosts()}
      </div>
    )
  }
...
5. Posts.js should end up looking like this:
// app/javascript/components/Posts.js
import React from 'react'
import axios from 'axios'
class Posts extends React.Component {
  state = {
    posts: []
  };
  componentDidMount() {
    axios
      .get('/api/posts')
      .then(response => {
        this.setState({ posts: response.data.posts });
      })
  }
  renderAllPosts = () => {
    return(
      <ul>
        {this.state.posts.map(post => (
          <li key={post}>{post}</li>
        ))}
      </ul>
    )
  }
  render() {
    return (
      <div>
        {this.renderAllPosts()}
      </div>
    )
  }
}
export default Posts
6. Start the rails s in one tab, and run bin/webpack-dev-server in another tab
7. Visit http://localhost:3000/posts
You should see:
• Post 1
• Post 2
POST request
We'll represent the HTTP POST request process through the NewPost.js component.  This component will call the posts_controller#create Rails action in order to create a new post.
Rails controller action and route
1. Add a create action to the posts_controller
We will simulate that the post_controller#create action was successfully hit by rendering the params that the endpoint was called with:
# app/controllers/api/posts_controller.rb
  def create
    render json: { params: params }
  end
2. Add a :create route to the :posts routes
# config/routes.rb
  namespace :api, defaults: { format: 'json' } do
    resources :posts, only: [:index, :create]
  end
Form to create a post
1. In NewPost.js create a form to submit a new post
// app/javascript/components/NewPost.js
  render() {
    return (
      <div>
        <h1>New Post</h1>
        <form>
            <input
              name="title"
              placeholder="title"
              type="text"
            />
            <input
              name="body"
              placeholder="body"
              type="text"
            />
          <button>Create Post</button>
        </form>
      </div>
    )
  }
2. Capture the form data
We'll go about this by setState through each input's onChange:
// app/javascript/components/NewPost.js
import React from 'react'
class NewPost extends React.Component {
  state = {
    title: '',
    body: ''
  }
  handleChange = event => {
    this.setState({ [event.target.name]: event.target.value });
  }
  render() {
    return (
      <div>
        <h1>New Post</h1>
        <form>
            <input
              name="title"
              onChange={this.handleChange}
              placeholder="title"
              type="text"
            />
            <input
              name="body"
              onChange={this.handleChange}
              placeholder="body"
              type="text"
            />
          <button>Create Post</button>
        </form>
      </div>
    )
  }
}
export default NewPost
  
  
  Calling posts#create from React
Our NewPost.js component will need to make an HTTP POST request to our new posts#create endpoint.  To do this, we will use the Axios HTTP client we installed in the last section.
1. Import Axios into NewPost.js
// app/javascript/components/NewPost.js
import axios from 'axios'
...
2. Create a function to handle the form's submission
// app/javascript/components/NewPost.js
...
  handleSubmit = event => {
    event.preventDefault();
  }
  render() {
    return (
      <div>
        <h1>New Post</h1>
        <form onSubmit={e => this.handleSubmit(e)}>
...
3. POST the form data to the posts#create endpoint
The Rails route for the posts#create endpoint is /api/posts.  We will console.log the response.
// app/javascript/components/NewPost.js
  handleSubmit = event => {
    event.preventDefault();
    const post = {
      title: this.state.title,
      body: this.state.body
    }
    axios
      .post('/api/posts', post)
      .then(response => {
        console.log(response);
        console.log(response.data);
      })
  }
4. Start the rails s in one tab, and run bin/webpack-dev-server in another tab
5. Visit http://localhost:3000/new_post, fill out and submit the form
At this point the form should not work.  If you look in the Rails server logs, you should see:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken)
This is a result of Rails' Cross-Site Request Forgery (CSRF) countermeasures.
Resolve CSRF issues
To resolve this issue, we need to pass Rails' CSRF token in our Axios headers, as part of our HTTP POST request to the Rails server-side endpoint.
Since this functionality will be required in any other future non-GET requests, we will extract it into a util/helpers.js file.
1. Create a app/javascript/util/helpers.js file
2. In helpers.js add functions to pass the CSRF token
// app/javascript/util/helpers.js
function csrfToken(document) {
  return document.querySelector('[name="csrf-token"]').content;
}
export function passCsrfToken(document, axios) {
  axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken(document);
}
3. Import the passCsrfToken function into NewPost.js and call it
// app/javascript/components/NewPost.js
...
import { passCsrfToken } from '../util/helpers'
class NewPost extends React.Component {
  state = {
    title: '',
    body: ''
  }
  componentDidMount() {
    passCsrfToken(document, axios)
  }
...
4. Visit http://localhost:3000/new_post, fill out and submit the form
In the console you should see:
params: {title: "some title", body: "some body", format: "json", controller: "api/posts", action: "create", …}
🎉
This tutorial series was inspired by "React + Rails" by zayne.io
              
    
Top comments (2)
I Finished your Tutorial! 3+ hours but finally finished and I'm so happy with the final result, I'm going to have to add some more things but it's going to look really nice!
Would you have any suggestions best routes for implementing CRUD I can't find a good helper that goes well with your tutorial unfortunately
Thanks for helping me bridge the gap between Rails and React! Very clear and useful!