React is Dead. Long live Reactive Rails! Long live StimulusReflex and ViewComponent!
Let me tell you about the feeling that I get when I encounter something poised to revolutionize my work as a software developer. A specific, tangible physical sensation in my bones; a tingle in my skin and a buzz in my fingertips. Like having done a little too much cocaine, if you know what I mean.
Apparently this mania hits me approximately every 5 years.
The first time was in 2000 when I read The Pragmatic Programmer and Kent Beck’s first XP book and they totally upended my ideas of what it meant to be a software engineer. Before that, to be honest, I was just thrilled with the money I was making, but craftsmanship and extreme programming turned me into such an evangelist I ended up founding Agile Atlanta.
The second time was in February 2005 when I wrote my first Ruby on Rails app together with Carlos Villela at ThoughtWorks. It was a clone of OkCupid, but for matching up people to be staffed together on consulting project teams, not romantically. I’m serious; it was called TWIX. (I still think it’s a cool idea!) Then I went on to lead some of the first paid “enterprise” Ruby on Rails projects in the world, and the rest is history.
I got the mania again in a big way in 2015 when the release of AWS Lambda made me an early and enthusiastic evangelist of serverless app design and got me so excited that I wrote a book about it. I was convinced it would be the next Rails for me, but life got in the way and trying to write a book about something that was changing so rapidly just didn’t work out. (I do plan to finish it someday, but I’m going to need some help to do so.)
Reactive Rails, eh?
Reactive Rails, assuming that’s what we’re going to call it, is my latest obsession. I think the name’s got a good ring to it, and part of what I’m trying to do here is signal-boost it.
Naming aside, this is not your mother’s Rails code. Adding StimulusReflex and ViewComponent to Rails changes everything. It gives me the same dizzying lightness of being that I felt in 2005 when I switched from J2EE to Rails to the extent that I’m blogging about it the same way.
…there is absolutely no technical need for Rails developers to use React anymore
A little bit of context
The last 3 years I’ve been focusing (sometimes full-time) on my music career as a performing DJ and electronic music producer. However, I still code regularly, mostly on side projects and and occasional paid gigs via my old consulting boutique Kickass Partners (now merged with MagmaLabs.)
Promo Guru is a side project that fellow music geek Ben Fraser and I have been working on for the last six months, making slow and steady progress towards an initial public release. Until this week, it was just a vanilla Rails app with some interactivity sprinkled in using JQuery.
I’ve been curious about StimulusReflex since seeing Nate Hopkins’ brilliant Twitter Clone in 10 minutes video. And the silly Russ Hanneman video totally put me over the edge as far as actually wanting to give it a try.
As anyone starting a new app on Rails 6 should do, I had already embraced Turbolinks and ActionCable on PromoGuru. The result was that getting StimulusReflex setup and climbing the initial learning curve felt relatively painless. The documentation is great too.
The first thing I did was to rewrite some non-CRUD controller actions as Reflexes and I was super impressed. Ben was like, “you’re not going to rewrite the whole thing before 1.0 launch, are you?”
No, that was probably not a good idea. But I did want to address some glaring anti-patterns related to our dialog boxes and audio preview player, and for that I remembered having read about the traction that a library called View Component has been getting. Plus, the StimulusReflex docs mention it prominently.
Attempts to introduce object-oriented views are almost as old as Rails itself, and I’ve never found them particularly compelling or convenient compared to just vanilla use of partials. But this incarnation of the idea seemed different, and the list of project contributors includes Tenderlove himself, so I decided to give it a go. Promo Guru is full of UX bits and bobs that feel like they want to be components anyway, so it felt like a productive experiment.
Armed with my new gems, I quickly ran into issues around trying to render components, which I fixed by upgrading from Rails 6 to edge (6.1 RC). It was painless, which is not surprising given that Basecamp, Shopify, and Github all run on edge, so it must be stable.
Once I got the hang of the pattern, I got so productive so quickly that it turned into a wild coding spree. Not only was I refactoring, I was also addressing some edge cases and adding little details that probably wouldn’t have been touched until much later in the project. I was ecstatic. I started feeling that tingle that I described a little while ago.
I’ve seen the future of Rails programming. It feels like React programming, except with less complexity…
And even though I didn’t keep careful track of my hours, I suspect that it took about 15 hours before I hit the first bleeding-edge kind of snags that I would normally expect to run into immediately. And it was at 3 am in the morning so it probably had more to do with exhaustion than the framework. Like I got up the next morning and cleared the hurdle in 10 minutes, you know?
Now a comprehensive description would literally take writing a small book, but let me try to summarize the approach in bullet points:
Reactive Ruby on Rails
- Uses the same RESTful application design you already know and love
- The canonical state of your application data is kept on the server-side (database and caching layer), just like a vanilla Ruby on Rails monolith.
- All template rendering happens on the server side using traditional Rails rendering techniques (I am a die-hard Haml evangelist and have never met a client-rendering technology that I didn’t instantly despise. JSX, ugh)
- The server-side controller layer is traditional RESTful style, but leaner because…
- Reflexes are tiny bits of code that alter server-side state in response to client-side actions, and take care of many interactions that would otherwise be coded as controller actions.
- When you invoke a Reflex, it does whatever it needs to do and the screen that the user is seeing is automatically re-rendered for you. I can’t overstate the magnitude of how much developer effort this saves!
- The use of regular JS-based Stimulus controllers is optional in this approach, but you quickly realize their usefulness. When matched with your Reflexes, they provide easy hooks for aspect-oriented enhancement of server-side interactions.
- …or you can write Stimulus controllers completely unrelated to Reflexes that handle only client-side interactions. (I’ll provide some examples below.)
- Just like React, only the parts of the DOM that need to be updated are ever updated, yielding some huge performance boosts, aka DOM diffing.
- For some actions you can dispense with DOM diffing altogether and change targeted parts of your UX using either CableReady’s comprehensive DOM-manipulation API, or StimulusReflex morphs. (The performance benefits are of doing that are immense!)
- CableReady makes it simple to trigger real-time DOM changes from server-side Ruby processes, because it’s all websocket-based.
- ViewComponent lets you replace a lot of partial templates and forces you to think about the building blocks of your UX in a componentized frame of mind (a good thing!)
- ViewComponent view objects are plain-old Ruby objects with well-defined interfaces, meaning that they’re ridiculousely easy to reason about and test and blazing fast compared to partials (10x speedup.)
- ViewComponent’s optional rendering functionality drastically reduces the amount of conditional logic you need in your normal view templates.
- Everything I’ve described above can easily be applied in a progressive fashion, meaning that it’s not an all or nothing affair. Make the MVP of your new project a plain vanilla Rails + JQuery app, then liven it up up with the stuff I’ve described above, which is exactly what’s happening on my current project.
What I just mentioned in the last bullet point above, progressive enhancement of a vanilla HTML app is a big, big deal, and probably what I’m most excited about if I’m being honest with myself. Progressive enhancement is the wise choice for most new web projects, and yet it almost never happens anymore.
The Building Blocks of Reactive Rails
Stimulus is what the Basecamp folks invented to augment your HTML with just enough behavior to make it shine (in their words.) Probably since I was already steeped in usage of React, it never caught my attention before this week. In fact, I felt a degree of hesitation about investing time in it since it seemed like a case of DHH being contrarian for the sake of it. I now think I was wrong about that.
ViewComponent by Github promises 10x rendering performance boost over partials.
Episode 196 - ViewComponent in Rails
The ViewComponent library will provide a new way for creating reusable and testable view components.
CableReady is the part of the overall picture that I’ve used the least so far, mainly because my initial impression was that it was simply a supporting library for StimulusReflex. It’s kind of RJS reborn on WebSockets. I won’t be talking too much about that, simply because I haven’t done much with it yet. However, I’m being promised that it’s an even bigger game changer than StimulusReflex.
Let me share some code from my first cut of rewriting the audio preview player from Promo Guru. Here’s a screenshot so you know what I’m talking about.
There are two view components reflected in this screenshot.
- A play button
- The player fixed to the bottom of the screen
I’m starting with the view component because I can actually show you what it looks like, and then we’ll get into server-side interaction.
I’m planning to make a lot of components, so I’m organizing them into modules representing functionality. So far I have
dropbox but in this blog post I’m only going to talk about the audio components.
There’s a couple helper methods sprinkled in there, but the only one relevant to this blog post is
Okay, so here’s where it starts getting interesting. In the first iteration of this functionality, we simply shoved a hidden player into the partial template for a track. That was grossly inefficient both in terms of rendering performance and bloating the size of our markup, but it worked well enough for an MVP.
So here’s what I ended up doing.
The key to Reactive Rails design is remembering that the server is the canonical source of truth for the state of the client (or something like that.) Even when it’s not obvious!
Taking that advice into account, when there is a track playing, a
track object could live in a
@track_to_play instance variable. And at other times it could be null.
Here’s how that approach plays out in the application layout.
The most interesting line there is 13, right smack in the middle.
<%= render Audio::Player.new(track: @track_to_play) %>
Note that to get render to accept a component object instance without ActionView freaking out you need Rails 6.1, which as of the writing of this blog post (October 2020) means you have to be running edge. As far as I’ve heard, Basecamp, Shopify, and maybe even Github run on edge. If you’re running a released version of Rails, there’s some workarounds and stuff that you’ll have to Google, it’s not worth getting into here.
So what was I saying? Oh yeah, I always render the component and sometimes the variable
@track_to_play is nil because the user isn’t listening to a track, and that’s fine, because if it’s nil, then the component knows not to render. All without involving any conditional (if/else) code in the template.
class Audio::Player < ApplicationComponent
attr_reader :track def initialize(track:)
@track = track
end def render?
render? method is part of the ViewComponent API. It’s hard to overstate how much conditional logic it will let me take out of my templates.
Moving on, how does
@track_to_play get set? Using a Reflex!
When you invoke a Reflex, it does what it does, and then StimulusReflex automatically re-renders the current screen and sends it down to the client, where DOM diffing magic is used to figure out how little of the screen we can get away with refreshing.
Click the play button and presto, changeo! The player appears at the bottom of the screen and starts playing. Speaking of the play button, here’s what that looks like in use.
Non-server Stimulus controller actions
With the new reactive approach, all I need is to somehow change the state of that magic
@track_to_play variable on the server and I should be good. Everything else should happen automatically.
First I need to write some plain-ole Stimulus actions.
The most important things to notice in this code, extracted from the player’s template, is that the top level element has a
data-controller attribute that identifies which Stimulus controller to connect to this element. It’s what will receive stimulation, so-to-speak.
When you mix Stimulus with ViewComponents, make sure to note that module names will turn into prefixes in your markup. In this case, the name of the controller is
audio--player not simply
player. (This is the snag that I hit at 3 am in the morning that forced me to call it a night.)
Looking at the next action, it reads like this
So when a click event happens, invoke the corresponding action on this element’s Stimulus controller.
The summarized explanation of this code:
- Get the track id from the
.audio-playerroot element. (Line 2)
- Find its corresponding play button on the screen. (Line 3)
- Navigate the DOM to find the next play button on the screen. Yes, this needs more code to tighten it up. (Line 4)
- Use the
stimulatefunction to trigger a Player Reflex play action on the server.
That’s literally all there is to it. I know this is going to get more complicated as we continue to add features, but I’m satisfied that I’m going to be able to grow it in a clean and organized fashion.
A little extra StimulusReflex goodness
Dropping this in here not so much because it has to do with the whole reactive Rails idea, but just because it’s damned cool.
These are application-wide hooks for all reflexes. I’ve got a couple things going on so far.
- Add and then later remove a
waitCSS class to the body element whenever there’s server activity going on. This lets me easily animate a progress indicator and implicitly let the user know that something is going on that they should wait for.
- Some performance benchmarking. So far I’m very impressed with the responsiveness of the approach.
At the moment, Rails 6.1 and ViewComponent are not playing completely nicely together. If you do everything I showed you above, you’ll quickly figure out that picking up changes to ViewComponent templates requires a server restart. Ouch.
I did a little digging and was able to come up with a workaround pretty quickly:
It appears to be a regression, so I’m guessing it will get fixed sometime soon. The workaround is not the worst in the world, either.
This feels like the start of something big for me. Already thinking it’s got enough meat for a new book. And it seems like the community is gearing up to become a movement, not just a niche. So it’s probably a good time to hit that Follow button if you’re interested in what happens next!
And oh, one more last thing…