Rails5, ActionCable, Redux, and React: Walking through an example chat application
For those unfamiliar, Rails 5 introduces ActionCable (integrated websockets), and DHH published a quick demo of how it works. If you happen to be interested in diving into my example below on your own local machine (which essentially transforms DHH's example into Redux/React), I'd recommend spending a few minutes watching his webcast first, as I borrow some of the exact code.
How Redux and React work together
Assuming you understand the basic idea, here's a great diagram of how Redux simplifies the architecture:
In other words, React is fantastic for keeping UIs snappy and organized, but web developers are left to keep track of the data and state of the application on their own.
Now, under the hood, in the example Rails application I pieced together (modeled mostly from Kenta Suzuki's react-rails-redux-sample), the react-rails gem helps setup a folder structure in the asset pipeline that can be tweaked a bit for Redux like so:
Actions in Redux are functions that pass along the action type and whatever else you want to send to your reducer. In this example, the chat.js file in the actions folder looks like this:
That is, we have an action that sets the messages up in the store (lines 4–9), and another that adds a message to the store (lines 11–16). That's it.
The reducer then looks like this:
Which contains a simple switch statement to send the action and the state into the right place in order to return the appropriate new state (a new object that your app listens to, which is important…you don't want to modify your old state).
At this point, if you really want to dive in, I'd recommend watching (for free) all 30 of Dan Abramov's intro videos. It takes a couple hours to plow through 'em, but your time is well spent; he walks through not only how Redux works, but why — from the ground up — it does what it does.
Containers and Components
Per Dan's videos, I went ahead and split up my React components into a “dumb” and “smart” component, termed component and container, respectively. The reason to do this, which Dan gets into, is to make it easier to map your state to properties (and dispatch binding) in the container, and let your “dumb” components just do the React (view-layer) stuff.
Thus, our chat app container file looks like this:
….which will make a ton more sense when you read the react-redux docs, but in short, it passes the part of the state that your “dumb” component should be concerned about, and ensures the proper dispatch binding so you can send actions to your reducers.
Thus, the “dumb” component of our chat app (now we're in React territory) is free to house the specific meat of our simple chat application:
There are a few things going on in here, so I'll walk through ‘em:
- I do a quick preventDefault() (line 10) so that our form doesn't submit when we press enter. :)
- Lines 13–18 listens to every keystroke and submits our message to ActionCable when we press enter. The subscription to the web socket (i.e. the code that DHH uses) was setup within the componentWillMount() function here in the Root container.
- Lines 21–31 are the rendered component itself, with some React-ness to display our list of messages and receive a new message typed in.
….where index.json.jbuilder looks like this:
(so we load the last 10 messages)…and the message partial is simply this:
json.extract! message, :id, :content
This way our index.html.erb file can simply be:
<%= react_component(‘Root', render(template: “messages/index.json.jbuilder”) %>
…so when our Rails app loads, it sends that initial state (the message objects in JSON format) to our Redux/React application.
Finally, in the MessageBroadCastJob file that DHH talks about, I just tweak it a bit to return JSON:
If you're interested in seeing a live version of this app, check out https://rails5reduxchat.herokuapp.com/ (this is hosted on Heroku's free tier, so if no one has hit the app for awhile it may take 20 seconds or so to fire up).
It took a bit of work to get the production environment setup for Redis/ActionCable, so check out specifically how I tweaked the cable.coffee file and updated the production.rb config file to include the action_cable configs for production. All-in-all it wasn't as bad as the tutorials out there say it can be, largely because most of the heavy lifting has already been incorporated recently into Rails 5.
Finally, if you're bold enough to try this on on your local machine, follow the instructions within the source code README file here.
Author's note: I'd love to hear from anyone with suggestions on how to improve the code/conventions used. While I'm now following the react-redux-universal-hot-example project and appreciate what the authors are up to there, it will take more time to loop in the [awesome] patterns I see (e.g. redux modules). Finally, subscribe to my newsletter and I'll let you know when I get new content up. Let's keep the conversation going. Thanks!
We help entrepreneurs ideate, validate, create, grow, and fund new ventures.
Based on decades of startup experience, our team has developed a comprehensive operations framework, a collaborative online tool, and a personalized coaching experience to help take your business to the next level.