This post is intended specifically for students at Flatiron School with project requirements that involve using both a React frontend and a Rails backend. From doing several projects with this setup, including a very challenging ActionCable implementation in Mod 4, I wanted to pass on a few nuggets of advice for using this stack. (And kudos to anyone else who finds these useful!)
First and foremost, my most important advice:
If you are using ActionCable, DO NOT RUSH into using the "React-ActionCable-Provider" package UNLESS YOU ALREADY KNOW HOW TO USE ACTION-CABLE!
This was my major mistake on my last project: since there’s not a lot of documentation/community help for React projects using a Rails backend with ActionCable, I rushed into using the react-actioncable-provider package without taking time to thoroughly learn it—or even ActionCable—beforehand. Sure, I went through some semi-related ActionCable tutorials and whatnot, but the moment I read a recommendation for that package, I jumped right in and never looked back.
Typically, I have tried to avoid using packages/gems/other bundled software in my school projects (or at least defaulting to using them) because I’ve worried about learning too much about the package and not enough about the core language I’m studying, or taking a shortcut and missing out on valuable under-the-hood learning. In this case, I screwed up on both counts—I spent a great deal of time trying to debug and work with the package itself, ultimately learning little about ActionCable or WebSockets outside of the project, and I know I will need more practice with ActionCable before I feel proficient at using it on its own.
Here are a few things I wish I would’ve taken the time to learn in ActionCable first:
1. Creating Subscriptions - this is something react-actioncable-provider abstracts away, so creating and managing multiple subscriptions became a confusing hassle. Here’s the code from the package—I recommend taking time to read through the ActionCable docs on Rails Guides and some tutorials to understand how to use each of the functions (received, initialized, connected, disconnected, rejected) work:
UPDATE: my cohort-mate Josh just published an excellent overview and tutorial for WebSockets and ActionCable, also from his Mod 4 project--this is a great place to start!!
var ActionCableController = createReactClass({
this.cable = this.props.cable.subscriptions.create(this.props.channel, {
received: function (data) {
onReceived && onReceived(data)
},
initialized: function () {
onInitialized && onInitialized()
},
connected: function () {
onConnected && onConnected()
},
disconnected: function () {
onDisconnected && onDisconnected()
},
rejected: function () {
onRejected && onRejected()
}
})
},
2. Building Auth into the Collection class - this is an important piece to include in programs that include games or chatrooms where you want to limit access to specific users—explicitly making use of and exploring the Connection class in ‘app/channels/application_cable/connection.rb’, and doing so early, helps prevent problems refactoring and debugging later on. Once again, Rails Guides comes through with the docs:
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
3. Deploying to Heroku - I don’t even know what to say about this one…because I still don’t fully understand which last-minute configurations made the app magically work on Heroku literally four minutes before presenting it. Just, make sure you leave yourself plenty of time, and try deploying an ActionCable tutorial app on Heroku ahead of time to know what to expect.
The react-actioncable-provider is certainly a powerful package, and I definitely owe any successful ActionCable functionality in that project to the package’s creator, Li Jie--but I would’ve been better off learning ActionCable first, and not trying to learn that AND the package at the same time.
DON’T RUSH IN! LEARN ACTION-CABLE FIRST!!
Rails Tips
Here are a few handy Rails pointers/tricks I picked up during the project:
ActiveSupport::JSON.encode() and ActiveSupport::JSON.decode()
The project involved using ActionCable to broadcast huge JSON-formatted arrays of lines and pixel data from Canvas drawings, as well as store them as strings in a database to be retrieved and redrawn later. This led me to the handy Rails module, ActiveSupport::JSON, which has two useful methods: .encode() and .decode(), which functions very similarly to the JSON.stringify() method you’re probably familiar with in Javascript:
.encode() will turn a JSON object into a string:
ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
# => "{\"team\":\"rails\",\"players\":\"36\"}"
You can store this in a single string or text cell in a database!
.decode() will take a JSON-formatted string and return a Ruby hash:
ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
=> {"team" => "rails", "players" => "36"}
snippets from the ActiveSupport module docs
This encoding/decoding strategy can get very inefficient, especially the larger the JSON object, but jamming the whole thing as a string into one cell in a database is pretty fun!
In Rails, you can access JSON sent by a Javascript fetch() request by using params[“_json”]
On the frontend, I ended up sending fetch() post requests with stringified JSON to Rails like so:
patchMessage = (newMessage) => {
fetch(`${API_ROOT}/messages/${this.props.message.id}`, {
method: "PATCH",
headers: HEADERS,
body: JSON.stringify(newMessage)
})
}
In a pinch, and with flagrant disregard for strong params, we accessed that JSON from the request inside the controller using params[“_json”]—here’s an example, in case you end up a similar pinch:
# /app/controllers/messages_controller.rb
def update
@message = Message.find(params[:id])
@message.update(text: params["_json"])
render json: @message
end
React Tips
And finally, a few React tips/tricks:
Passing Props with React-Router (yes, it makes code look ugly)
If using React-Router is a project requirement, you may find yourself needing to pass props through a component, instead of the component it's passing through as a prop. Props (har har!) to Tyler McGinnis for this solution:
<Route exact path="/" render={(props) => <UserForm {...props} rerenderApp={this.rerenderApp} />} />
So: create a render={} prop with an arrow function passing (props) to your component, and specify that component's props there like normal.
Don’t wanna update state? Use this.forceUpdate() instead
This was a trick I attempted while trying to get window scrolling and resizing to rerender some components...I don't think it worked, but I stumbled across the this.forceUpdate() method, which allows you to force a rerender without setting state:
handleResize = () => {
this.forceUpdate();
};
Save yourself a deployment headache—store all URLS (such as API endpoints) as constants in one file
This is probably just good coding hygiene, but make yourself a constants.js file that holds all your relevant constants--like API endpoint URLs and fetch() request headers--in one place. Your life will be so much easier when you deploy on Heroku and need to swap out the URLs. Don't forget to export them!
// /src/constants/index.js
export const API_ROOT = "https://draw-n-discuss-backend-rails.herokuapp.com/";
export const API_WS_ROOT = "ws://localhost:3000/cable";
export const HEADERS = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
Conclusion
I hope you Mod 4 students find something helpful in here! Good luck on your projects, and please feel free to add any further suggestions in the comments!
Top comments (0)