How to implement a news feed in React.js - Part 2

This is the second post in a series of posts about React.js. In the first post, we have developed a news feed in React. In this post we will:

  • Create a back-end in Rails (source code available at this repository on GitHub);
  • Build the communication beetween the React app and the Rails app.

The source code of the React project we have developed on part 1 is available in this repo.

The changes we made at the React project are in the same repo but in another branch.

Different ways of integrating React and Rails

One possibility is to write React code inside a Rails project. As of Rails 5.1 release, it’s easier to work with javascript frameworks within a Rails application.

One of the new features of Rails 5.1 is the Yarn support and the Webpack support through gem webpacker.

If you want to use this approach in a Rails 4 project, you can use the gems react-rails and react_on_rails.

Another possibility is to keep React code and Rails code in separate projects and make them talk to each other through an API. This is the approach we are going to use in this post.

Creating a Rails API application

As of Rails 5 it’s possible to create API-only applications:

$ rails new news_feed_rails --api

When this command runs, Rails creates a lighter application, without the resources that would be used by traditional applications with HTML views.

The application is configured to use only the necessary middlewares. With this setup, ApplicationController inherits from ActionController::API, which in turn imports a smaller number of modules.

The Rails application we are going to create is very simple. It has only the Post model:

$ rails g model post category:integer content:string

We will add an enum for the categories attribute and leave category and content as required fields:


class Post < ApplicationRecord
  validates :category, :content, presence: true

  enum category: {
    world: 1,
    business: 2,
    tech: 3,
    sport: 4
  }
end

Let’s take care of the controller:

$ rails g controller posts

We need two actions in our controller: one to list the posts and another one to create a new post:


class PostsController < ApplicationController
  def index
    posts = Post.all
    render json: posts, status: :ok
  end

  def create
    post = Post.new(post_params)
    if post.save
      render json: post, status: :created
    else
      render json: { errors: post.errors }, status: :bad_request
    end
  end

  private

  def post_params
    params.permit(:category, :content)
  end
end

Finnally we update routes.rb:


Rails.application.routes.draw do
  resources :posts, only: [:index, :create]
end

Our API is done! But since we are going to make the requests from the React application, we must enable requests from other domains.

Rails 5 makes things easier to allow CORS (cross-origin HTTP request). Just uncomment the gem rack-cors in Gemfile:

gem 'rack-cors'

And uncomment the contents of the file config/initializers/cors.rb:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

To make things simpler, in origins '*' we allow requests from all domains.

Let’s start the app at the port 3001 (in order to let the React app running at port 3000):

$ rails s -p 3001

Done!

How to consume an API from a React app

Before we begin, let’s navigate to the React project folder and start the server:

$ npm start

In the javascript world there are a bunch of ways of sending HTTP requests, as you can see in this article.

In this post we will use the Fetch API, which is available as of Firefox 39 and Chrome 42.

The Fetch API provides the method fetch(), it has only one required argument, the URL of the resource we want to access, and returns a Promisse with the response.

Let’s start by adding the method fetchPosts() to the component Feed:

fetchPosts() {
  fetch('http://localhost:3001/posts').then((response) => {
    return response.json();
  }).then((posts) => {
    this.setState({ posts });
  });
}

Note the we use fetch() to make the request. We deal with the Promisse in order to get the response in the JSON format through the call to response.json() and finally we have an array of posts, which we use in order to update the state of the Feed component.

Using React lifecycle methods

React components have a lot of lifecycle methods we can override when we want to run a specific code, as an API call, at particular times.

Let’s override componentWillMount() in order to make our request. This method runs before mounting occurs, before the call to render():

componentWillMount() {
  this.fetchPosts();
}

Let’s change the code to initialize the list of posts with an empty array (the former implementation loads the list of posts from the localStorage):


class Feed extends Component {
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      filteredPosts: []
    }

Our News Feed already loads posts from the API!

However, it would be interesting if we repeat the request from time to time in order to load new posts without the need to refesh the page. Let’s create the method startPolling then:

startPolling() {
  this.timeout = setTimeout(() => this.fetchPosts(), 10000);
}

Note that we store the timer used by the method setTimeout() in this.timeout. We will use this reference to cancel the timer when component unmounts:

componentWillUnmount() {
  clearTimeout(this.timeout);
}

We need to change the method fetchPosts() to call startPolling() when the first request finished:


  fetchPosts() {
    fetch(`${apiUrl}/posts`).then((response) => {
      return response.json();
    }).then((posts) => {
      clearTimeout(this.timeout);
      this.startPolling();
      this.setState({ posts });
    });
  }

This way, the flux of calls is something like this: componentWillMount() -> fetchPosts() -> startPolling() -> fetchPosts() -> (…).

Making POST requests

In order to make a POST request, we pass a second argument to the method fetch():


  handleNewPost(post) {
    fetch(`http://localhost:3001/posts`, {
      method: 'post',
      body: JSON.stringify(post),
      headers: { 'Content-Type': 'application/json' }
    }).then(function(response) {
      return response.json();
    }).then(function(data) {
      console.log('server response', data);
    });

    var posts = this.state.posts.concat([post]);
    this.setState({ posts });
  }

Here we make a POST request and update Feed optimistically though the call to setState(), in order to leave the UI more fluid.

In order to add category correctly, we must change the list of categories to match the enum of the model Post from the Rails app. In the code we wrote in the previous post, the first character of each category name was uppercase. We will be fine if we change everything to downcase:


const categories = ['world', 'business', 'tech', 'sport'];

Displaying validation errors

But what if we have validation errors? In this case, it would be nice to:

  • display validation error next to the corresponding field;
  • remove from the list of posts the new post we have added optimistically.

Let’s start by changing the method handleNewPost():


  handleNewPost(post) {
    const currentPosts = this.state.posts;
    const context = this;

    var posts = this.state.posts.concat([post]);
    this.setState({ posts });

    fetch(`http://localhost:3001/posts`, {
      method: 'post',
      body: JSON.stringify(post),
      headers: { 'Content-Type': 'application/json' }
    }).then(function(response) {
      return response.json();
    }).then(function(data) {
      if (data.errors) {
        context.setState({
          errors: data.errors,
          posts: currentPosts
        });
      } else {
        context.setState({
          errors: {}
        });
      }
    });
  }

Take a look at the lines 65-74. Here we write the code that treats the errors. Remember that the Rails API returns a list of errors if the model has validation errors. If we have validation errors, we update the state with the list of errors and we reset the list of posts (lines 67 and 68).

At the lines 52 and 53 we store the current list of posts, it will be used if the model has validatin errors, and we passed this to the variable context. This is a trick to use the reference this of Feed inside the callback.

Let’s initialize the errors object at the state:


class Feed extends Component {
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      filteredPosts: [],
      errors: {}
    }

Before changing PostForm, let’s pass the list of errors to it via a prop. The change to the Feed component is following:


  render() {
    const posts = this.state.posts.map((post, index) =>
      <Post key={index} value={post} />
    );
    const filteredPosts = this.state.filteredPosts.map((post, index) =>
      <Post key={index} value={post} />
    );
    return (
      <div className="feed">
        <Filter onFilter={this.handleFilter} />
        {filteredPosts.length > 0 ? filteredPosts : posts}
        <PostForm onSubmit={this.handleNewPost} errors={this.state.errors} />
      </div>
    )
  }

Finally we update the component PostForm in order to display validation errors:


 render() {
    let errors = {};
    Object.keys(this.props.errors).forEach((key) => {
      errors[key] = this.props.errors[key] ? this.props.errors[key][0] : null;
    });
    return (
      <div className="post-form">
        <form onSubmit={this.handleSubmit}>
          <label>
            Category:
            <small className="error">{errors.category}</small>
            <select ref={(input) => this.category = input}>
              {categories.map((category, index) =>
                <option key={category} value={category}>{category}</option>
              )}
            </select>
          </label>
          <label>
            Content:
            <small className="error">{errors.content}</small>
            <input type="text" ref={(input) => this.content = input} />
          </label>
          <button className="button">Submit</button>
        </form>
      </div>
    )
  }


At the lines 133-136, we put in the object errors the first error of each attribute, if there are any.

Our implementation is finished! You can see the code running in this CodePen here.

Next Steps

Our code works, but it looks like the Feed component is doing too many things. In addition to having the responsibility to take care of its state and props, the component is concerned with making requests to an external server.

In the next post we will learn how to manage the data of our React app using redux. See you there!

Written on 10/20/2017