Six years later it remains informative for weighing the cost of going with (or against) the grain of what Rails is good at.
Rails reigns as the best tool for rendering structured forms, processing the submission of those forms, and responding with the new state of the world.
Providing a partial
Six Years Later
This approach begins to feel uneasy when an
app/views/dodads/update.js.erb template gains more and more imperative responsibilities.
In addition to adding the new
@message record to the list and highlighting it, what if we wanted to clear the form?
What if saving
@message fails, and we want to communicate errors?
I’ve written, approved, and shipped plenty of
update.js.erb templates that end up looking something like:
<% if @message.persisted? %> $('#messages').prepend('<%=j render @message %>'); $('#<%= dom_id @message %>').highlight(); $('form#<%= dom_id Message.new %>').reset() <% else %> $('#<%= dom_id @message, 'errors' %>').replace( '<%= j render partial: "generic_errors", errors: @message.errors %>' ) <% end %>
Add a few more conditionals or animations and you’ll arrive at the crossroads where a team wants to exchange the imperative management of
DOM nodes with a declarative tool like React.
Something unintentional happens when the decision gets made to introduce React.
The Server-generated JavasScript Response baby gets thrown out for the new hotness bathwater.
An assumption creeps in that moving views from server rendered
ERB templates to client rendered React components implies replacing our battle tested (and integration tested) Rails CRUD controllers with yet-to-be built
This assumption is mistaken.
So what might Server-generated React responses look like?
Let’s take the
update.js.erb templates from the original post and change it to render a React component.
Let’s call this hypothetical component
update.js.erb start to look very similar.
Rails traded away the responsibility of knowing what to insert, where to insert it, what to highlight, and how to highlight it.
Rails gained the responsibility of reloading the entire state of the
Message.all world into the
@messages instance variable.
Now let’s address our product’s need to communicate errors if the
@message doesn’t save.
Now a pattern is emerging. We’re passing the state of the world from Rails to React. Rails is the brain where application logic and state lives. React is the dumb renderer.
Why does this matter?
All we did was move some rendering from an
ERB partial to a React component.
We didn’t fundamentally change the server/client communication of our Rails application.
We never had to
bin/rails generate controller api/messages or
require("axios") to begin producing and consuming an api.
We dipped our toes into a client-side rendering experiment for one high-fidelity view.
Selling your team on rewriting the application to a
JSON api is too large a bet, and you’re likely to introduce regressions.
Replacing the rendering responsibilities of two
ERB templates with one component is a low-risk move that gives React the opportunity to prove it can carry it’s own weight as you add it to your stack.
You don’t have to ditch Rails UJS and
data-remote="true" to get started.
My team has written high-fidelity forms that are rendered with
ReactDOM.render, submitted using
data-remote="true", and respond with a
ReactDOM.render to communicate the result.
These forms have been chugging along for a few years now, serving our customer’s needs.
This hybrid approach of Rails UJS and React is not going to win any awards for functional paradigm purity.
But we’re not submitting for that award.
Our reward is customers paying us because they trust the reliability, and our team feeling confident to make changes with deterministic results.
⚠️️️ Security ⚠️
A quick note about security.
diffs above are presented to be minimal.
If you ever begin rendering
<%= @record.to_json %> within
ERB templates, do it securely by wrapping it with
<%= raw(json_escape(@record.to_json)) %>.
If you think you’ll be calling
ReactDOM.render a lot, feel free to grab this ReactHelper that I’ve found useful.
There’s a few React idioms and happy accidents to share that make this approach very powerful.
Try experimenting with React state and multiple calls to
I was blown away when I learned the state is maintained between calls, because React doesn’t care if you’re re-rendering and diffing props at a root, or deep in a tree.
Watch the state get blown away when you need it to by calling
ReactDOM.render with a new key prop.
Explore the concept of controlled and uncontrolled components.
With an idiomatic prop such as
defaultMessages, some React state, and a little hook around diffing a
candidateMessage prop, we could revert form passing the entire
@messages collection to passing the maybe-created
@message and earn back our performance gain.
Dive deeper into the Rails way and build your props with JBuilder.
Too Long; Scrolled to the Bottom:
There’s a lot of ground to explore for integrating React and Rails without assuming a Single Page App and a
When you want to employ React for high-fidelity views, don’t trade away the things Rails has become extremely good at over the last fifteen years.
Take smaller steps.