How to Build a Twitter Clone with Rails, ActionCable and React
Tags: rails, ruby, react
Published: 2020-05-04 08:50:57

---
description: A response to building a Twitter clone with Rails, CableReady, and StimulusReflex
---

Let me start by saying I think the things the teams working on CableReady and StimulusReflex are pretty awesome. They are aiming to make working with Reactive web applications as easy as the first Rails Blog tutorials during Rails' infancy.

With all of that being said, I am someone who prefers a bit more explicitness and work with tools I already know (and well, a lot more developers in the ecosystem).

I am not a video guy, don't have a video set up, and _really_ prefer not to hear the sound of my own voice. So this will be done through words!

Without further ado, let's get started with a new app:

```console
rails new blabber --no-spring --webpack=react
```

Rails will do its thing, install the application, install the gems, process the Webpacker install, and install the NPM packages needed for React.

We can jump right into making a model to hold the data to clone what would be a tweet in this Twitter clone. All basic attributes:

```console
rails g model Post username body:text likes_count:integer repost_count:integer
```

To keep this closely resembling the CableReady/StimulusReflex, we'll add the same validation in the `Post` model:

```ruby
class Post < ApplicationRecord
  validates :body, length: { minimum: 1, maximum: 280 }
end
```

We'll make a few small adjustments to the generated migration file to add some database-level defaults (and allows us to keep the code around `Post` creation simple):

```ruby
class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :username, default: 'Blabby'
      t.text :body
      t.integer :likes_count, default: 0
      t.integer :repost_count, default: 0

      t.timestamps
    end
  end
end
```

Ok! Now, we're ready to run that migration!

```console
rails db:migrate
```

With the Model and Database layer out of the way, we can move on to the controller and corresponding view templates!

```ruby
class PostsController < ApplicationController
  def index
    @posts = Post.all.order(created_at: :desc)
    @post = Post.new
  end

  def create
    Post.create(post_params)
    ActionCable.server.broadcast(
      'PostsChannel',
      Post.all.order(created_at: :desc)
    )
    redirect_to posts_path
  end

  def like
    Post.find_by(id: params[:post_id]).increment!(:likes_count)
    ActionCable.server.broadcast(
      'PostsChannel',
      Post.all.order(created_at: :desc)
    )
    redirect_to posts_path
  end

  def repost
    Post.find_by(id: params[:post_id]).increment!(:repost_count)
    ActionCable.server.broadcast(
      'PostsChannel',
      Post.all.order(created_at: :desc)
    )
    redirect_to posts_path
  end

  private

  def post_params
    params.require(:post).permit(:body)
  end
end
```

Simple controller. The `index` action returns a list of posts, to `@post`. `create` uses `StrongParameters`, creates a new Post, broadcasts a message over Websockets (more on that soon), and redirects back to the `index` template. `like` and `repost` are similar except they increment the respective count columns.

Let's wire up a few routes to match up to those controller actions. Yes, these aren't perfect RESTful routes, but 1) They work. 2) This is a 10-minute tutorial. 3) Are GET requests making sure we do not have to worry about AJAX/fetch/CSRF in the front-end. You would obviously work around these issues in a production application.

```ruby
Rails.application.routes.draw do
  resources :posts, only: %i[index create] do
    get 'like'
    get 'repost'
  end

  root to: 'posts#index'
end
```

With a Model, Controller, and routes, we can put together some view templates. We'll start by adding the `action_cable_meta_tag` and `Bootstrap` CDN CSS. This way, we can wire up some UI interfaces pretty quick!

```erb
<!DOCTYPE html>
<html>
  <head>
    <title>Blabber</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= action_cable_meta_tag %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>
```

First up is the `app/views/posts/index.html.erb`:

```erb
<div class="container">
  <h1>Blabber</h1>
  <h4>A Rails, Actioncable and React demo</h4>

  <%= render partial: 'form' %>

  <%= react_component("PostsWrapper", { posts: @posts }) %>

</div>
```

`react_component(` is a view helper that is included in `react-rails`, a gem we will install in a minute. Even if you don't use every feature in the gem, it offers a great way to include a component into an existing view file and the `props` for its first load.

Up next is a straightforward Rails form:

```erb
<%= form_with model: @post, local: true, html: {class: 'my-4' } do |f| %>
<div class="form-group">
  <%= f.text_area :body, placeholder: 'Enter your blab', class: 'form-control',
  rows: 3 %>
</div>

<div class="actions">
  <%= f.submit class: "btn btn-primary" %>
</div>
<% end %>
```

Alright, that is all we need with ERB files, no we can move onto the `ActionCable` pieces.

First, we'll edit the `Connection` file to identify the Cable connection with the browser session ID:

```ruby
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :session_id

    def connect
      self.session_id = request.session.id
    end
  end
end
```

Next, we'll create a new `Posts` channel:

```console
rails g channel PostsChannel
```

...and specify the channel we will be using in the stream by a string, `PostsChannel`:

```ruby
class PostsChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'PostsChannel'
  end
end
```

That does it for the `Actioncable` backend!

Next up us the `React` side of the application. As we installed `React` with the Rails application command, we can jump to making sure that `react-rails` gem in installed:

```ruby
gem 'react-rails'
gem 'webpacker', '~> 4.0'
```

With the `Gemfile` update, we can install the gem, use its installer, and add a package to helper connect to `ActionCable` in functional `React` components.

```console
bundle install
rails generate react:install
yarn add use-action-cable
```

Almost there! We have two React components to add. First up, is a wrapper component that will allow you to wrap your true child component in the `ActionCableProvider`, which provides access to `ActionCable` through a `React Context`:

```jsx
import React from "react";
import Posts from "./Posts";
import { ActionCableProvider } from "use-action-cable";

export default function PostsWrapper(props) {
  return (
    <ActionCableProvider url="/cable">
      <Posts {...props} />
    </ActionCableProvider>
  );
}
```

Inside the provider, there it passes the `props` to a `Posts` component. The `Post` component:

```jsx
import React, { useState } from "react";
import PropTypes from "prop-types";
import { useActionCable } from "use-action-cable";

const Posts = props => {
  const [posts, setPosts] = useState(props.posts);

  const channelHandlers = {
    received: data => {
      console.log(`[ActionCable] [Posts]`, data);
      setPosts(data);
    }
  };

  useActionCable({ channel: "PostsChannel" }, channelHandlers);

  return (
    <React.Fragment>
      {posts.map(post => {
        return (
          <div key={post.id} className="card mb-2">
            <div className="card-body">
              <h5 className="card-title text-muted">
                <small className="float-right">
                  Posted at {post.created_at}
                </small>
                {post.username}
              </h5>
              <div className="card-text lead mb-2">{post.body}</div>
              <a className="card0link" href={`/posts/${post.id}/repost`}>
                Repost ({post.repost_count})
              </a>
              <a className="card0link" href={`/posts/${post.id}/like`}>
                Likes ({post.likes_count})
              </a>
            </div>
          </div>
        );
      })}
    </React.Fragment>
  );
};

Posts.propTypes = {
  posts: PropTypes.array,
  header_display: PropTypes.string
};

export default Posts;
```

This might be the most complicated file in the whole tutorial! First, we set up some internal state for `Posts`. This allows us to set the incoming `posts` prop as the state, to update when `ActionCable` passes new posts from a broadcast. `channelHandlers` sets up the handler for this `ActionCable` subscription to handler new data. Finally, for `ActionCable` setup, `useActionCable` ties the handler and channel name into a new front-end subscription.

The return statement returns the JSX template for each post. It is mostly Bootstrap markup but does include two links to reach the earlier created controller actions. As GET requests, they will follow the redirect and reload the `index` template.

There you go, at this point, it should look like this!

![Blabber Gif](https://robrace.dev/build-a-twitter-clone-with-rails-actioncable-and-react/blabber.gif)

There you go! I bet with a fast enough system to work through the Rails install, gem install, and javascript package installs, you could make it through this tutorial in less than 10 minutes!