Creating Chatrooms with ActionCable in a Rails/React Application

Andrew Julian
8 min readMar 6, 2023

--

This blog will cover the steps for implementing chatrooms in a single page application that utilizes a Ruby/Rails API and React Frontend.

Before you begin, this blog assumes that you have a Rails / React application that includes user login and authentication, as well as the creation of different and unique “rooms” in which you can add the chats.

Below is a link to my GitHub Repository for a Rails / React project that implements the ActionCable technology for chatroom implementation.

For styling, I utilized TailwindCSS, but any styling protocol would work. To read more about TailwindCSS, visit https://tailwindcss.com/.

Initial Setup: Gems and Routes

Before we get started working to create controllers and components, we need to ensure that our gems and routing are properly setup to allow for action cable to eventually be implemented.

In your Gemfile, you will want to be sure the gem “rack-cors” is present. Once this is the case you can run a “bundle install” to update your gems.

If you used create-react-app, the gem will be in the file, but commented out.

After this, you will want to navigate to your config file in your rails app. Specifically we should be in the initializers folder in the “cors.rb” file. In here we will need to uncomment the following code and change the origins location in order to allow for middleware.

Lastly, we will need to create a route for where the channel will mount. Add the following line of code to your routes file, found in the config folder.

mount ActionCable.server => "/cable"

At this point we are ready to work with the React frontend and create a place where we can write, send, and display messages.

Step #1: Creating the Chatroom Component (React)

As this is more of a tutorial on implementation of Action Cable in rails, the code below is a template that will create an area for populating messages (to be demonstrated later), as well as a form for writing the message body and a button to activate the sending/creating of the message.

The process for creating messages on the front and back end will be covered later. This just gives us a template for display.

Step #2: Creating the Channel (Rails)

The next step will be to create the channel or channels that users will subscribe to in order to post and receive messages.

First you will need to create the channel by creating a rails channel using the command…

rails g channel chat

This will create a channel folder within your rails app folder. In here you will see a file titled “chat_channel.rb” which will hold information for subscribing and unsubscribing to a channel.

If you are going to have multiple channels, you will need to utilize params to specify the channel to which the user is subscribing (as in example below). If you are just creating a single channel, you can just use a string to name the channel in place of the params.

We will see later how the params are used, but in this case we are set up to start receiving messages channels, as well as stop receiving messages from channels when we choose to leave. These methods will be called in the front end, when seeking to connect to a channel.

Step #3: Connecting to the Channel (React)

Now that the methods for subscribing and unsubscribing are created, we need to connect to the channel.

In order to do this we need to create an instance of a WebSocket, which can be done using the following line of code. This creates a channel using the route we specified earlier.

By creating this we can then run methods created for that class on the newly created instance in order to manage the websocket functions.

The first of these methods allows the user to connect to the channel. In this example we use ws.open which runs when the component is mounted and then ws.send which sends information to the chat_channel.rb files with the command to “subscribe” and the needed information. In this case that information is the channel name as the channel param (remember [:channel] in the subscribed method) and the unique user, which in this case is the currentUser.id.

Each connection to a channel needs a unique id. In this case it was just easiest to use the user id, rather than some other value.

At this point the user is connected to the channel and is able to receive messages when they are sent by any other user connected to the channel.

Step #4: Creating Messages (Rails)

With the form already created in the React component and being sent to the back end with a POST request using the function below…

… we can receive that data in our messages controller to create the message.

If you don’t already have a messages resource, make one using…

rails g resource Message

With this you will then have a controller, serializer, model, and also migration created for your messages.

First we need to create the migration. For my example, my messages belong to a classroom and a user. Thus, my migration looks like this…

After adding your needed elements, make sure to run “db:migrate” in the terminal to update your schema.

Now that we have the migration, we can update the controller to create the message and store it in the database. Since the message belongs to a user and a classroom, I will create it within the user and associate it with the classroom using the parameters.

Now that the message is created, it is stored in the database. The issue we now have is that we need to broadcast the message to those in the channel.

Previously, when we created the POST request, a manner to handle the returned item was missing. This is because we will handle the display of the message using the controller and another Websocket method.

In the model, we want to utilize the capability of action cable in order to broadcast the message to the channel. In the code below, we will run the broacast_message method after a new message is created.

Since the user (and all other users) is/are already subscribed to the channel, the message is ready to be received by the front end.

Step #5: Receiving Messages (React)

Now that we can send and receive messages, we need to consider how to store and then eventually display messages.

Before we think about receiving new messages, we should retrieve previous messages in the channel so that even new users can see the entire thread of content on the channel and not just new messages sent since they subscribed.

With a useEffect and GET request, we can retrieve and store all messages to a state variable called messages in the chatroom component.

I am using a function called setMessagesAndScroll as I have a feature that stores the messages and automatically scrolls to the most recent message. You can check the Classroom.js component for that code.

When the component is launched the previously sent messages will be stored in messages state. Using a map method you can display these in the previously created chatroom component (see step #1).

For my messages, I displayed the user profile picture, display name and then the message. This also included a ternary statement for handling when a user does not have a picture uploaded.

Since this displays all of the previously sent messages, we need to handle those that are sent during a broadcast.

If you recall we had a method in the model that broadcast a message to the specific channel once the message was created. Now, using another Websocket method called, “onmessage”.

With this method the broadcast message is received and then using the code below, the new message is parsed and then stored in the message state variable.

With an update to the state variable, the component will re-render and re-populate the messages for display, including the new message.

Step #6: Closing the Connection (React)

At the point where you want to disconnect from the channel, possibly to switch channels, you need to unsubscribe. Similar to the connection on mount, we want the unsubscribe to happen when the page changes or the component unmounts.

Just as with the original connection and other Websocket updates, there is a method for this called “close” that runs when the component is closed.

In the code above, a request is made to the “chat_channel.rb” with the command to unsubscribe a specific user with the unique id from the channel specified in the parameter.

Automating this unsubscribe feature allows the switching between channels to be effectively optimized so that a user does not have to remember unsubscribe via a separate action.

--

--