About a year ago, I created an embedded React chat component within a standard page of a Rails app I was working on. I used PubNub for the realtime messaging of that chat component. I recently wanted to learn how to use Action Cable and how it handles realtime WebSocket communication between the server and client. I decided to use the same chat component within a clone of the app to learn about Action Cable as quickly as possible. At first, I was just going to write about the Action Cable setup but then I thought that maybe I will show how to use the PubNub service first and create a two part series that compares the two ways of handling realtime communication.
This is a tutorial describing how I implemented both versions of the chat component. I know what you’re thinking – not another chat app tutorial. One reason for the chat example is because this is not a standalone chat app but rather a React chat “component” that is embedded in an existing standard Rails page that complements the other features of the page. I thought that some readers could possibly benefit from this slightly different implementation while learning how to integrate a React component that uses realtime communication within an existing Rails page. Anyone reading this should be able to replace the chat component with any other type of realtime data service.
The key technology stack components consists of the following:
- Ruby on Rails
- React on Rails
Part 1 of this series will show how to build the embedded React chat component using the awesome React on Rails gem. This setup will also use the Webpacker gem which greatly simplifies Webpack configuration. We will then add the PubNub API code for the communication piece as an alternative to Action Cable. Part 2 of this series will use the same Rails app and React chat component but will use Action Cable and WebSockets for realtime communication. The reason the Action Cable portion is second is because we will need to add more Rails code to get it to work plus I created the PubNub version first anyway.
Throughout this tutorial, I’ve tried to include the most pertinent info needed to get this app working correctly while keeping it as concise as possible. Some prior knowledge of the technologies used would be beneficial. I recommend other more basic tutorials if you’ve never used React or Redux. Please see the documentation for more details of the libraries we will be using. This tutorial also assumes your Rails app is already created. I will not be going over how to create a new one. Let us begin with Part 1.
Overview of the Existing Rails App
The existing Rails app is called Grow and Swap. The idea of the app is to provide an online space for home gardeners to post extra produce they may have (i.e. tomatoes, eggplant, etc.) to trade with other gardeners’ produce near them. The key component is the “trade offer” page. This page displays a trade offer that a user has created which shows what item they want from another user. The business logic of the app allows the other user to browse and pick items from the first user that they would like to trade in return.
I originally added a React chat component on this same page so that the two users could communicate with each other on any particular details of the trade like where and when to meet for the trade or whether they would rather ship their items. The chat “channel” or “room” is confined only to the one
TradeOffer instance while other instances have their own unique channel. Also, there can only be two users for each
TradeOffer. Other users would not be able to see the chat thread. I won’t be getting into the details of the
TradeOffer Rails code except for where it ties in the React on Rails helper methods.
Add Webpacker and React on Rails
We will be using the React on Rails gem to provide some convenient helper methods in our layout,
TradeOffer#show view page. We will also be using Redux to help manage our state. React on Rails provides helper methods for this as well. Webpacker is a gem that simplifies the use of Webpack for a Rails app and helps to conform to the Rails “Convention over Configuration” doctrine. If you need basic Webpack configuration, then this gem is highly recommended. If you are creating a commercial grade app, you may want to add the Webpack module to your
package.json file and configure it yourself. Also of note is the Webpacker gem requires Node.js 6.0.0+ and Yarn 0.25.2+ according to the docs. Let’s begin by adding the latest
webpacker gems to the Gemfile:
bundle install and the following two commands to install webpacker with React:
Don’t forget to use the
rake command instead of
rails if you are not using Rails version 5 or later. The React option will configure Webpacker for React, add the appropriate Yarn dependencies, and a React “Hello World” example. This will create the following directories which is where our React components will live:
Also added is a
webpacker.yml file in the
config folder. It is organized around the three default Rails environments: development, test, and production. This file contains the basic configuration for Webpack similar to a
webpack.config.js file you may find in frontend apps but the Webpacker version is in YAML format. There shouldn’t be anything you need to modify in this file for most configurations. Things like css/sass loaders, hot module replacement (HMR), and hot reloading of assets should work out of the box. Webpacker adds other config files as well. If using Rails versions earlier than version 5, then Webpacker should create a package.json file in your app root directory and add the appropriate npm packages. You probably also noticed that
yarn install was automatically ran by Webpacker. Please see the Webpacker GitHub page for more information on Webpacker inner workings and configuration.
Next, commit your changes to git before running the React on Rails installer. Run the React on Rails installer with the help option to view a list of options:
Then run the installer with the Redux option:
This will of course create and modify a lot of files and add gem/npm packages most of which I won’t get into in this article but you can checkout the React on Rails docs for more details. The important piece that it creates is a “hello world” React-Redux example with all of the files needed for it to work. You can model your own React-Redux components off of this example and then delete the “hello world” files later if you wish. Also created are Procfiles in the root directory since React on Rails recommends using Foreman to manage your app startup. Ensure you have Foreman installed on your system:
gem install foreman. The React on Rails npm package is also added to the
package.json file. The version of this package must match the React on Rails gem version in your
After the installer finishes, there should be a print out of next steps instructions. Follow these steps as necessary. We will cover them as we build our chat component. Try running the app and the “hello world” React-Redux example should work:
Make sure you reference the correct Procfile/script with this command. You may need to add a few packages to your package.json file. This is what mine looks like:
prop-types will be used for type checking. Some of the above packages were added before using Webpacker and may not be needed. Webpacker provides a lot of them packaged with the gem. Simply add what you need to this file later if any dependency errors come up. Once you have added your packages, run
Rendering the Chat Component in the Rails View
We will start by adding React on Rails helpers to the existing Rails app. There are a lot of moving pieces between React on Rails and the frontend React/Redux code to keep them connected so I will start by a high level walk-through of the relevant files before going over the Rails code in detail.
When a user clicks on a specific trade offer, Rails will route the request to the
trade_offers#show action in
trade_offers_controller.rb. In the
show action, objects are retrieved as normal for the page and then props are initialized for the Chat component and passed to the React on Rails
redux_store helper. When the page loads, the
react_component helper in
show.html.erb is where the Chat component will be rendered. Since we need this component to be hydrated with initial data by React on Rails, we need to have the
redux_store_hydration_data helper preferably in the main layout
application.html.erb and at the end of the file so that it loads after the view’s HTML.
From there, React and Redux takes over updating the state as long as the user stays on that page. In other words, the page is now a single page app!
Let’s now go over the Rails code in detail. Add the React on Rails controller extension to the application controller so that all controllers inherit the controller/view helpers:
Next, let’s add the trade offer controller code:
The trade offer code here is not of that much importance for this tutorial but it is placed here to show where you would pull your data from the Rails database (don’t worry, the before action filter pulling the trade offer from the database is there just not shown). For this specific app, the PubNub channel is set by the initiating trade offer ID and passed to the
channel prop as a string. This is how only two trade offer pairs will have their own private chat channel. The other props
newMessage are initiated with an empty array and string respectively. This initial state is saved as an instance variable and then passed to the
redux_store helper along with the name of the store we want React on Rails to connect with.
The controller will respond and render the following show page:
This helper is all that is needed on the show page for this component to render. That is pretty unobtrusive! You can wrap this in element tags if needed but I recommend to keep all HTML and CSS with the component. In this example, the component is placed at the end of the file but it can be placed anywhere on the page.
The last piece for the Rails side in this example is the
This helper will render a hidden
div with the Redux store hydration data. This should be placed in the main layout below
yield so that the hydration data will render after the HTML. This is recommended by React on Rails in case there is a large amount of data. That is all that is needed on the Rails side and we are now ready to move on to building our chat component client side code.
Building the Chat Component
React Component File Organization and Layout
The chat component we’ll create will be structured pretty much the same as the “hello world” React-Redux example that was installed by React on Rails. Here is the file structure for the frontend component files (with the “hello world” example deleted):
If you’re new to React and Redux, you’re probably thinking that’s a lot of files just for a Chat frontend component. The code we’ll be writing in these files could be written in one but we will be following React on Rails and Redux recommendations for the app structure. This will help immensely if more React components are added to your app later by keeping the pieces well organized.
Entry Files and Component Startup
packs directory contains the entry files while the
application.js. This will be your top-level file for importing all of your React components similar to the
application.js file that lives in the
assets directory. In this case, it will be one component:
Next, add the following code to the source entry file imported above:
Starting at the top, we import the React on Rails npm package that was added to the
package.json when we ran the installer. Next, we import the top-level
ChatApp file that lives in the
startup directory, then import the
chatStore file for the Redux store. The next couple of lines is where we register the
chatStore with React on Rails. This is how we connect the frontend code to Rails. According to the React on Rails docs, we register components and mount them with Rails view helpers instead of mounting components directly to elements with
React.render. It is important to remember this when referencing React docs as this may cause some confusion.
Next, let’s go over the top level React component file
ChatApp.jsx. Here, we will call
ReactOnRails.getStore so that props from the Rails controller/view can be passed into the Redux store. React on Rails generated comments are left in for clarification:
chatStore is passed in as an argument to
ReactOnRails.getStore. Remember that this store was registered with React on Rails previously. The store is then passed to
Provider to bind it to the React container component.
You see that we have the
<ChatContainer /> component nested within the
<Provider> React-Redux component. This is how the
chatStore is made available to the Chat component without passing the store as a prop. If you ever want to add another React component to your Chat component, just reference it here. For this tutorial, we just have
Before we get too deep into the Redux code, let’s now look at this React “smart” container component and the actual chat code:
We import the React
Chat presentational component and return the Redux “connected” version of it as the comments show. This is how our Chat component will actually connect to Redux. The
mapStateToProps() function returns an object containing the state the chat component will need passed as props. The
mapDispatchToProps() function returns an object containing the actions (or redux action creators) that binds to the Redux dispatch method for actual dispatching of actions. These actions are passed to the chat component as props similar to the state. Finally, we call the React-Redux binding
connect() to connect the Chat component to Redux.
Let’s go through our actual Chat component code. This is where we will spend most of our time because most of the functioning Chat code will go here including the PubNub API code later on. First, let’s create our Chat class:
Instead of writing pure functions, we need to create an ES6 class so that we have access to React’s lifecycle methods. Along with
React, we’ve imported
PropTypes so that we can do some type checking on our props to aid us in development and prevent bugs if this were to be deployed to production. First, we call a
static method for this type checking on our Chat props and actions. Next, we initialize our props with a call to
super and bind our class methods to the
Chat instance in the constructor method. Don’t worry, we will be going through each of these methods. Feel free to add additional methods to this class, just don’t forget to bind them to the instance here.
Now to rendering the Chat component. Let’s start by adding some empty
div elements to the
chat-container div is for CSS styling purposes only. The
chat-component div will wrap the two main sub-components –
message-input. Let’s now add the
These are wrapped in a couple of
span elements so that they render next to each other. The
textarea has a
ref attribute that sets a reference to the
textarea for the
autosize() method we’ll write later. This is the latest syntax for creating ref’s as of React version 16.2.0. The value attribute is set by the
newMessage props as a user types in their message. This is a React “controlled component” with state that updates the Redux store for each character entered. The
onChange event calls the
onNewMessageChange event handler for each character entered. The
onKeyPress event calls the
onNewMessageKeyPress handler when the user hits enter. Lastly, we have a
button onClick event as well. Let’s go over each of these event handler methods (remember these were defined in the constructor):
This event handler passes the event value to the
changeNewMessage() action that we’ll go over later during the Redux code overview. All actions are passed down as props from the
chatStore. This method then calls the
autosize() method to handle automatic size changing of the
textarea element as the message text wraps to a newline. I won’t get into the details of this code but this is what it looks like:
The one thing I will point out is the
textArea ref we set earlier in the
textarea element. The ability to use refs is one of the benefits of using a React class component versus a pure functional component. If implemented correctly along with CSS styling, our
textarea should grow upwards line by line up to a limit and then start to scroll after that. Once the
newMessage is submitted, then the
textarea will reset back to it’s default one-line height. I’m sure
autosize() could be implemented as a controlled component and the state changed via a Redux reducer but we will leave it as is for this tutorial. Feel free to implement this yourself.
The next event handler is as follows:
This event handles the user pressing
enter to submit the message. You could also omit this handler altogether if you prefer
enter to simply create a newline and only allow
onSubmitClick() to handle message submit. This handler passes the current
newMessage prop to the
onSubmitClick() event handler:
This handler also passes the current
newMessage prop to the
submmitNewMessage() method. We will go over this method later in the tutorial when we connect to the PubNub API.
That almost covers everything we need for the presentational portion of the
message-input component. Another reason we use the ES6 class is to access the React lifecycle methods for connecting to PubNub and other behavior that we will cover soon. So far, this is what our
message-input looks like:
This screenshot is the finished product. You won’t be able to render the component until all the required React and Redux code is finished. The screenshot is just here to show what the
message-input will look like. Notice the component is embedded in a Rails page with other elements above it.
Next, lets write our
messages component code. We will import a
MessageList component from another file since we already have quite a bit of code in our
import statement to the top of the file below the other
import statements and then add the
<MessageList /> element to the
messages div created earlier. The
messages props are passed to the messages attribute. Also take note of another
ref added called
messagesDiv. We will get back to what this ref is used for when we add some styling and manipulate behavior.
Let’s take a look at the
ChatMessageList file to see what’s going on. We want to render a list of the messages that are passed down as props from the Redux store. Let’s write a function that returns this list:
First, import the usual
PropTypes for our component. You’ve probably noticed that we don’t import any Redux modules in our presentational components –
ChatMessageList.jsx. These two files are React code only and don’t know anything nor care about Redux. This code would still work if you don’t want to use Redux. We will connect to Redux soon in our container component.
We are writing pure functions in this file since we don’t change local state or require lifecycle methods. The
MessageList() function simply uses
Array.map() to return a list of messages if messages exist, else it returns “No messages”.
To display the time of the message, the
timeOfMessage() function takes the message
id as an argument (this id uses
Date.now() as its unique identifier as will be shown soon) and returns either the time of message if during the same day or the date of message if more than one day has elapsed:
The only thing remaining in this file after the
timeOfMessage() function are the
PropTypes declarations and exporting
Let us now connect to the PubNub API for our realtime messaging functionality. PubNub is a great tool for this with an easy to use but comprehensive API for pretty much any messaging behavior needed. Its global data stream network provides very efficient low-latency realtime messaging and is an alternative to creating a Rails model and controller to save and retrieve messages. The key to this is the PubNub Storage and Playback feature that allows you to call up a history of messages from their storage. There is a default limit of 100 messages. You can call up additional messages with 100 messages per page but if you really need more reliable persistence, then PubNub offers a storage add-on with different tier levels. Of course, using Rails to persist messages to your own database is always an option.
PubNub uses the publish/subscribe pattern and thus requires a publish key and subscribe key to connect to the API so the first thing you need to do is signup for an account on their website. During the signup process, you will create your publish and subscribe keys. Add the npm package with
yarn add pubnub. The code in this tutorial used to make the API calls is with version 4.19.0. See the PubNub website for migrating to later versions if the API has changed. You can also checkout this tutorial for a more in-depth overview. Next, add your import statement to
We will now create a new PubNub instance using the publish and subscribe key created during the signup step. This will be done in the
componentDidMount() lifecycle method so that anytime a user goes to the
TradeOffer#show page React will call this method just after mounting the Chat component which in turn will create the new PubNub instance:
ssl default setting is
false so set to
true if required for making requests over HTTPS. Additional method calls that are required in the
componentDidMount() method was already added. Let’s go through each of those.
pubnub.addListener() method is what listens for incoming new messages that have been published either by the current client or other clients. This method has a callback function
message() that we use to grab the response with the message payload and pass this to the
incomingMessage() action. This action adds the new message to the messages array in the Redux store.
pubnub.subscribe() method is pretty self-explanatory. We use
this.props.channel passed down from the store. We saw earlier how the initial state of this channel was created in the
The last method call is
fetchHistory(). This is our own class method defined as follows:
We call the
pubnub.history() method to load previously created messages when we load the Chat component. See the PubNub docs for details of how to use this method. We have set this to call the last 30 messages assuming that users that want to trade produce probably don’t need to have a long discussion about their trade. This can be omitted if you want the default last 100 messages. The key part of this method is the use of promises that enables us to call up an appropriate action to handle the response to this method call. We call up the
addHistory() action and pass the history messages array included in the PubNub response.
There is one more method to go over in our Chat component – submitting a message:
This method starts with creating a message object consisting of
content properties. We create a unique ID by setting it to the current date and time that the message is created. The content is set to the text argument (recall that the submit and key press event handlers pass the
newMessage props to this method). We then call
pubnub.publish() to publish the new message to the connected channel. The callback function handles any status errors that may arise. Finally, the
submitMessage() action is called which triggers the
newMessage reducer to simply update the state by returning a new empty string. Then the
textArea ref is used again to call the
focus() method on the
This should cover all the working components of connecting to the PubNub API. As you can see, it’s fairly simple and straightforward.
We now have our presentational components completed except for some styling. Add
import './chat.scss' below the other
import statements in both
ChatMessageList.jsx and create the following stylesheet:
It is now time to write our Redux code including the actions, reducers, and store. We’ll try to keep the description of each one of these pieces to a minimum. The Redux documentation can be referred to for more detail. Let’s start with our actions:
These functions are “action creators” that return action objects as the Redux docs describes. You’ve probably noticed that these functions or action creators are the same ones that are passed down as props to our Chat component. There isn’t anything too unordinary about these action creators except with
incomingMessage(). Due to the object shape that PubNub returns in their API response, we need to match that shape when the client submits a new message. This is done with the payload object that includes an “entry” object with the message.
Redux recommends using string constants for the action
type property. The constants imported above looks like this:
Next we will cover the reducers, this is where the rubber meets the road when using the Redux architecture. Redux recommends spending a good amount of time thinking about the app state shape. I’ve found this idea to be very useful because creating your reducers haphazardly can lead to confusion about the app state shape later on. You may not know what your state objects look like or how many levels of nesting they may include. I’ve found that it helps to create a “road map” so to speak by documenting the state shape in a comment in your reducer file:
Keeping the state shape nesting to a minimum is always a good idea. As mentioned earlier, This shape reflects the messages array in the response from PubNub. I would rather this be flattened out a bit and could probably be done but we will keep it as is for this tutorial. Now that we have our shape, we can start writing our reducers:
combineReducers() from Redux to combine all of the reducers into one. We also import the chat constants just like in the
chatActionCreators file. The
messages reducer is the most important one here for our Chat component. It utilizes the ES6 array spread operator to update the state of the
messages array by first creating a copy and updating the copy, thereby avoiding mutating the original array. When the
addHistory() action is triggered, the reducer appends the
pubnub.history() array to the current array. When the
incomingMessage() action is triggered, the reducer appends the single message returned by the PubNub response.
newMessage() reducer updates the
textarea value as the user types their message. This is what makes the
textarea element a React “controlled component”. This reducer also clears the
textarea when the
newMessage is submitted. The
railsContext() reducer is primarily used for setting the initial state of the
railsContext props that is available from React on Rails. It isn’t required to be used at all. It is available for meta data purposes that includes things like URL settings. Finally, call
combineReducers() to combine all the reducers in one and export that as
Now we can create the last piece of the Redux code, the store that is used to hold state:
We create a function called
chatStore() that is a “storeCreator” as React on Rails calls it. React on Rails comments were added in the code above that helps explain what’s going on. We create a
newProps constant with the props object passed in using the object spread operator to copy all of its properties and the
railsContext. Next, we call
createStore() imported from Redux and pass in the imported
newProps. The second argument is how React on Rails hydrates the initial state of the Chat component.
That’s all for the Redux code and our Chat component is now completed. Here is what the final product looks like with a couple of messages entered:
We have now completed our Rails embedded React Chat component by showing how to use the React on Rails gem to seamlessly add the component to a Rails page, building our React component itself, connecting to the PubNub API, and using the Redux pattern for maintaining state. That was a lot of code for such a small component but the good thing is a lot of this code can be used for other React components with minor changes for naming of props, data, and functions as well as designing the business logic of the reducers. When you start to add a lot of React components to create your dynamic web app, all this extra code will actually help to keep your components organized and keep concerns where they need to be to manage the state of those components.
Some companies may prefer to keep their React frontend codebase separate from their backend Rails app and make JSON requests but the React on Rails gem comes in handy when you prefer to keep all of your codebase in one app. The helper methods it provides allows the developer to pass data between the frontend and backend without dealing with JSON requests and responses.
That is all for now. Stay tuned for Part 2 of this series where we’ll show how to use this same Chat component with Action Cable and WebSockets for realtime messaging instead of PubNub.