This morning I read Live previews with Rails and Stimulus 2, a post which compares the experience of implementing a text preview feature using server-rendered code. The author choose to use a "pure Ajax" approach with Stimulus, as well as what the same project would look like using Turbo Frames.
This post will present a 3rd option: StimulusReflex. The full source for this example is available on Github. Don't forget to change to the previews branch!
First, we'll need a template. I'm just going to use a vanilla tag helper:
<%= text_area_tag :body,
nil,
data: { reflex: "input->Composer#preview" } %>
<div id="preview"></div>
The only thing required to make this feature work is a Reflex declared on the textarea element. input
events captured will initiate a preview
action on the Composer
Reflex class. I also assigned an id
attribute to the strong
element so that we have a destination for the preview content.
class ComposerReflex < ApplicationReflex
def preview
morph "#preview", element.value
end
end
The preview
action takes the value
of the element which triggered the Reflex, and updates the #preview
element in the browser. Behind the scenes, this is done using one of CableReady's 33 operations, inner_html
.
That's it... there are no further steps. You don't need any extra templates, routes or Stimulus controllers.
If you open the Console Inspector, you'll see that each Reflex takes about 5ms, round-trip.
StimulusReflex and CableReady offer a simple but powerful RPC model for building reactive applications. The functionality of this stack eclipses Turbo in every meaningful dimension:
- CR has ~7x as many verbs/operations as Turbo Streams (just CRUD)
- CR supports adding custom operations
- CR is incredibly flexible in terms of who or what you send updates to
- CR operations can be chained and target multiple elements
- CR can broadcast from anywhere in your app: jobs, reflexes, controllers, rake tasks, channels
- CR can provide visual feedback when elements are updated
- SR provides a transaction wrapper around each request
- SR has a full life-cycle of events and callbacks on the server and client
- SR Reflex updates use DOM diffing, not innerHTML, so Stimulus controller state is preserved
- SR Reflexes can be initiated by JS or declared in markup
- SR offers a highly performant RPC mechanism with Nothing Morphs
- SR benefits from tight coupling with Stimulus
- SR has really powerful server and client debug tooling
- SR has exceptionally thorough documentation
- SR has incredible free support
- SR has a 2000+ person Discord community that is incredibly supportive
I hope that you'll consider updating your post with a third option, Josef!
Top comments (8)
Interesting!
Am I right in assuming that in the following snipped:
We can now wrap "element.value" with any kind of Ruby method like a call to process Markdown?
Anything that ultimately ends up emitting a String, sir!
While I agree that SR and CR are much more powerful than Turbo. I think comparing them is like comparing Apples and Oranges. It bugs me that so many folks are trying to compare the two when CR and SR are just different solutions in this same problem space. I don't think people should be trying to pick between SR and Turbo, they should be evaluating how they want to build an application and what they want to support and choosing the appropriate tool. They don't need to be competitors.
I think (?) we agree in spirit. We see Turbo Drive as the successor to UJS and, like UJS, we go out of our way to recommend that developers at least consider whether their needs can be addressed by those built-in tools.
The problem is the DHH himself positions Turbo as a superior alternative to SR, so we're stuck tackling FUD scenarios on a 1:1 basis where people are attempting to use the built-in tool to solve extremely complex UI scenarios for which SR/CR is designed for - and they are doing it because someone they trust made it.
If Turbo Drive had been released by an unknown developer, people would not be setting aside vastly simpler and more powerful tools to use it. Adding routes and keeping track of dozens or hundreds of "frames" is not, with any objective distance, a positive.
Turbo is not even really "baked" in yet. My efforts to use Turbo in a few Rails apps I work on have gone really poorly. I'm super unimpressed. As a "add-on" for applications already in development Turbo (imo) sucks. It's impossible to make things "work" without rewriting a bunch of work that already exists.
Either way, I hear you. I wish DHH would be more on board with "other" solutions that are not his own. I hear him when he doesn't really wanna change the way "the internet works" with websockets doing funky things and dom diffing etc. But, SR/CR is powerful in so many different ways than Turbo.
Don't doubt for a moment: Hotwire and to a lesser extent Kredis are the marquee features of Rails 7. I don't know if you've noticed like we have but he responds to critiques with "still in beta" and when people ask if it's going to have major updates before Rails 7, he says "beta is just a word".
All I know is that if we don't stand up for and tell people about this thing we love (and have invested, cumulatively, thousands of hours into, for free) then nobody is going to do it for us - even if DHH didn't have a bad case of Not Invented Here syndrome.
Thanks for the thought provoking convo! I hope you can forgive a certain degree of parental pride in what we've created.
Yeah, I know. I'm trying to be excited about it haha. I think it's fine. I really like Stimulus and I'm very excited to see the mobile "hotness" that is supposedly going to come out of Turbo. But I just know it's gonna pigeon hole me into writing and app in the same way that DHH would write it. While I don't think that is a bad thing, I don't think it's a good thing either.
I don't think you have enough parental pride! I'm using and generally loving every minute of working on an app with SR around. It's quite nice.
Flattery will get you everywhere!
We were saying yesterday that the biggest gift that Hotwire gives us is the assumption that Stimulus will be available by default. I don't think this gets nearly as much attention as it deserves - because it's freaking awesome.