Rails with React - One Repo - Part 3
Tags: rails, react
Published: 2018-11-14 22:29:09

---
title: Rails with React - One Repo - Part 3
published: true
description: Tutorial series that walks you through creating a Ruby on Rails web application that uses React as its view layer, all in one repository.
tags: rails, react
cover_image: https://thepracticaldev.s3.amazonaws.com/i/j0xrwz8vsxkvc5b4nndy.png
---

## Part 3 of 3: Handling requests between React and Rails

### Recap

In parts 1 & 2 of this series we covered:

- [setting React as the view layer](https://dev.to/harrymlevine/rails-with-react---one-repo---part-1-3k7o)
- [integrating React router](https://dev.to/harrymlevine/rails-with-react---one-repo---part-2-4a6o)

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:

1. User visits a URL
2. An HTTP request is made to this URL
3. The path is identified in `routes.rb`, and calls the associated controller action
4. The controller action executes its logic
5. 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](https://api.rubyonrails.org/classes/ActiveSupport/Inflector/Inflections.html#method-i-acronym) explains this as:

> specifying any word that needs to maintain a non-standard capitalization

```ruby
# 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.

```ruby
# 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.

```ruby
# 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](https://www.npmjs.com/package/axios) HTTP client.

**1.** Install Axios

```
$ yarn add axios
```

**2.** Import Axios into `Posts.js`

```jsx
// 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](https://medium.com/@_ChrisBradshaw/blogdidmount-2018-guide-to-react-component-lifecycle-methods-1614e0bbe80a).

Note that the Rails route for the `posts#index` endpoint is `/api/posts`.

```jsx
// 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

```jsx
// 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:

```jsx
// 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:

```ruby
# app/controllers/api/posts_controller.rb

  def create
    render json: { params: params }
  end
```

**2.** Add a `:create` route to the `:posts` routes

```ruby
# 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

```jsx
// 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`:

```jsx
// 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](https://www.npmjs.com/package/axios) HTTP client we installed in the last section.

**1.** Import Axios into `NewPost.js`

```jsx
// app/javascript/components/NewPost.js

import axios from 'axios'
...
```

**2.** Create a function to handle the form's submission

```jsx
// 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.

```jsx
// 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](https://guides.rubyonrails.org/security.html#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

```jsx
// 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

```jsx
// 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](https://www.youtube.com/channel/UCkoEStUK7wxmZef2DcPuCAQ/featured)_