Using ReactJS with Rails Action Cable

Screenshot from 2016-08-23 17-06-44.png

Introduction

Action Cable integrates websocket based real-time communication in Ruby on Rails applications. It allows building realtime applications like Chats, Status updates, etc.

Action Cable provides real time communication. ReactJS is a good tool to manage view complexity on the client side. Together they make it easy to develop snappy web applications which requires state management on the client side without too much work.

Anytime data changes the new data is instantly provided by Action Cable and the new data is shown on the view without user doing anything on the application by ReactJS.

In this post, I won't detail on Action Cable and Reactjs, but I will code a Simple Chat Application using both.

If you are a newbie for them, please read about actioncable and reactjs to understand about theirs basic first.

Install react-rails

 gem 'react-rails

then bundle install

Next, run the installation script:

rails g react:install

More detail about how to install reactjs-rails, see the following link: react-rails

Create MessageBox Component

React is all about modular, composable components. For our message emple, we'll have the following component structure:

  • MessageBox
    • MessageList
    • MessageForm

Let's starting from the children of the Components. Add following components to app/assets/javascripts/components/message.js.jsx

MessageList Component:

var MessageList = React.createClass({
  render: function() {
    var messages = this.props.messages.map(function(message) {
      return(
        <li key={message.id}>{message.content}</li>
      )
    });
    return(
      <ul>{messages}</ul>
    )
  }
});

MessageForm Component:

var MessageForm = React.createClass({
  getInitialState: function() {
    return {content: ""};
  },
  handleContentChange: function(e) {
    this.setState({content: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var content = this.state.content.trim();
    if (!content) {
      return;
    }
    this.props.onMessageSubmit({content: content});
    this.setState({content: ""});
  },
  render: function() {
    return(
      <div>
        <form className="message" onSubmit={this.handleSubmit}>
          <input type="text" name="content" placeholder="Chat here ..."
            value={this.state.content} onChange={this.handleContentChange}/>
          <input type="submit" value="Send" />
        </form>
      </div>
    )
  }
});

MessageBox Component:

var MessageBox = React.createClass({
  loadMessages: function() {
    $.ajax({
      url: "/messages",
      dataType: "json",
      cache: false,
      success: function(data) {
        this.setState({messages: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error("Cannot load data.");
      }.bind(this)
    });
  },
  handleMessageSubmit: function(data) {
    $.ajax({
      url: "/messages",
      dataType: "json",
      type: "POST",
      data: {message: data},
      success: function(data) {
        this.setState({messages: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error("Cannot load data.");
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {messages: []};
  },
  componentDidMount: function() {
    this.loadMessages();
  },
  render: function() {
    return(
      <div>
        <MessageList messages={this.state.messages}/>
        <MessageForm onMessageSubmit={this.handleMessageSubmit}/>
      </div>
    )
  }
});

After building complete Component, don't forget that we have to have controller to response to the ajax request.

class MessagesController < ApplicationController
  def index
    load_messages
  end

  def new
  end

  def create
    @message = Message.new message_attributes
    @message.save
    load_messages
  end

  private
  def message_attributes
    params.require(:message).permit :content
  end

  def load_messages
    @messages = Message.all
    render json: @messages
  end
end

Now we get the complete MessageBox Component. So we can render this reactjs component in view using:

<%= react_component("MessageBox") %>

Now you can start running your server:

rails server

And go to localhost: 3000, you will get your Chat Application is running. But it is not realtime yet. We will apply action cable for it later.

Apply ActionCable to React Component

First, add this code to routes to tell where it will listen to:

 mount ActionCable.server => "/cable"

Create MessageChannel

rails g channel Message

User will stream from a channel is called: channel_public_message

class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "channel_public_message"
  end

  def unsubscribed
  end
end

Then we will have to edit the following code in MessageBox Component to append message each time when we get broadcast.

  # ...
  setSubscription: function() {
    App.message = App.cable.subscriptions.create("MessageChannel", {
      connected: function() {},
      disconnected: function() {},
      received: function(data) {
        this.appendNewMessage(data);
      },
      appendNewMessage: this.appendNewMessage
    });
  },
  # ...

Then we should create Job to broadcast the message:

rails g job Message

After message is saved, we will start broadcast:

class Message < ApplicationRecord
  after_commit :broadcast

  private
  def broadcast
    MessageJob.perform_later self
  end
end

Now you have a full Simple Chat Application that realtime using Reactjs and ActionCable.

Conclusion

In this post, I don't explain too detail, but I give you the real example on how to apply action cable with Reactjs.

Complete Code and Demo https://github.com/yongsokheng/reactjs-actioncable

References

Facebook Reactjs Tutorial

React-rails Gem

Action Cable Rails