Now whenever the server sends data about a conversation to the client, it’ll only include the id and title fields for that conversation as well as an array of any associated messages.
Likewise, those messages will only include each message’s id, conversation_id, text, and created_at fields.
These minimal changes are suitable for our current needs, but Serializer is even more useful in other circumstances, such as when you want to share a user’s details, but not necessarily their password.
We can now finally generate our controllers:rails g controller conversationsrails g controller messagesThe ConversationsController’s index route is going to be used for the front-end’s initial fetch request to receive the current existing conversations and their messages.
The create routes for both controllers will be used for saving received data and broadcasting that data to the appropriate channels.
Two interesting things to note here.
One is that that ugly mess of code that gets assigned to the serialized_data variables is necessary for using Serializer with WebSockets.
Normally, we wouldn’t have to do this — we could simply write render json: conversations as we did in the ConversationsController’s index method.
However, since our create methods are broadcasting the data to our channels rather than rendering the JSON directly, we need to instantiate new Serializer instances manually.
It’s ugly, I know… but trust me, it’s worth it.
The second thing to notice is that we’re using two different methods to broadcast.
The first is the ActionCable.
broadcast method used in the ConversationsController, which accepts as arguments a string for which channel to broadcast to as well as the data to be broadcast (notice that this string corresponds to the string we provided in our ConversationsChannel’s subscribed method).
The second way of broadcasting data is the MessagesChannel.
broadcast_to method, which instead of a string takes an object from our models as it’s first argument.
This object corresponds to the one we specified in the MessagesChannel.
In general, I find stream_for and broadcast_to to be useful for transmitting data along non-universal channels, such as for members of a particular conversation or specific users.
I also want to mention that we don’t necessarily need to put the logic for broadcasting within our controllers.
We could’ve done this within the channels themselves, adding other methods on top of subscribed and unsubscribed, as this tutorial does here.
Or we could’ve delegated this functionality to Active Jobs, telling our application to automatically broadcast whenever a message or conversation is created, as demonstrated here.
But I’m including the logic within the controllers here because controllers are familiar, and I personally find it easier to learn new things within the context of the familiar.
With that, our back-end should be all ready to go.
On to the front-end!Setting Up React to Work With Action CableLet’s create our React app.
Back in the directory above your API, run:create-react-app my-appWe’re going to be using a module called react-actioncable-provider to incorporate the Action Cable functionality into our front-end.
I want to give a major shoutout to its creator, Li Jie — this package really simplifies and streamlines the whole process.
Run the following in your console:npm install -S react-actioncable-providerBefore we get started creating our components, lets lay down some core variables we’re going to be using repeatedly.
Within src, create a folder called ‘constants’ and within that, create a file called ‘index.
Fill it out like so:We’re defining these here to avoid repeating ourselves and so that later, if we deploy to production and the URLs need to be changed, we only have to change them in one place.
Notice that we have a separate URL for WebSockets, since they are their own protocol and do not use HTTP.
I want to approach this from the top down, so let’s start with our root ‘index.
js’ file, located directly within our ‘src’ folder.
We’re going to import the <ActionCableProvider> component from the react-actioncable-provider package, and use it to wrap our <App> component like so:We provide the <ActionCableProvider> component with a URL which corresponds to the /cable endpoint we set up in our Rails routes.
We’ll then edit our <App> component:I’ve removed everything that was inside of <App> and replaced it within a soon-to-be-created component called <ConversationsList> , which will effectively act as the top-level-component in charge of managing our application’s state.
Now it’s finally time to create the ‘components’ folder within ‘src’.
We’re going to have five components.
First, our top-level component, ConversationsList :This component fetches the data for any pre-existing conversations from our API when it mounts, and also has methods for adding new data to its state when the API broadcasts a newly created message or conversation.
Within the render method, we’re using a component called <ActionCable> which was also imported from the react-actioncable-provider package.
It takes two props, channel (Which specifies the channel to connect to, as well as any optional params as we’ll see in a bit) and onReceived, which handles what to do with any broadcasted data.
Our <ConversationsList> renders a list of <Cable> components.
js’ within your ‘components’ folder and edit it as follows:This component also renders <ActionCable> components, but this time gives them an extra parameter within the channel prop that corresponds to a given conversation’s id.
If you look back at the MessagesChannel we defined in our back-end, you’ll see that we use this parameter to select a conversation from the model and instantiate a connection for that conversation.
I’m also using the <Fragment> component here.
Fragments were introduced in React 16.
2, so they’re very new.
Since our <ActionCable> components don’t actually render anything themselves, it seems wasteful to wrap them in an empty <div> — thus the use of <Fragment>.
But if you’re on an older version of React, you should be able to replace <Fragment> with <div> and the functionality will be identical.
These components contain everything we’ll need connect our app to Action Cable.
The rest of the components are merely filling out what is necessary to make our app functional.
As such I won’t go too into detail on the rest, since there’s nothing here that shouldn’t already be familiar from React.
But you can still copy them:And that’s it!.Now if you create a new conversation/message, you should see it show up immediately.
And it’ll persist, so if you refresh the page it’ll still be there.
ConclusionThis blog post is getting pretty long, so I’m going to end it here — with what we’ve covered, hopefully you’ve learned enough to be able to start incorporating Action Cable into your own React apps.
There are two other things I had wanted to cover.
First, in order to really see the benefits of WebSockets, you’ll need to deploy to Heroku or some other platform where multiple people can use your app simultaneously.
If that’s something you’re interested in, you can follow the steps here.
The only additional steps you’ll need to take are changing the API_ROOT constants we defined on the front-end in the ‘index.
js’ file of the ‘constants’ folder, as well as potentially adjusting your CORS allowed origins settings on the back-end.
Secondly, I would highly recommend dissecting the functionality of the react-actioncable-provider module we used.
It’s short, only 142 lines of code, and it should help clarify exactly what these components are doing and how you could potentially recreate their functionality in a non-React environment.
Please feel free to reach out if you have any questions or suggestions.
That’s all, folks!SourcesAction Cable Overview – Ruby on Rails GuidesAction Cable seamlessly integrates WebSockets with the rest of your Rails application.
It allows for real-time features…edgeguides.
orgReal-Time Rails: Implementing WebSockets in Rails 5 with Action CableIt's been one year since Action Cable debuted at RailsConf 2015, and Sophie DeBenedetto is here to answer the question…blog.
comIntegrating Action Cable with React, the “It Works” Waytl;dr: Read it or implement polling.