Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Live previews with Rails and Stimulus 2 (strzibny.name)
119 points by nilsandrey on March 23, 2021 | hide | past | favorite | 49 comments


Based on the feedback, I updated the post with one more approach based on the idea from https://news.ycombinator.com/item?id=26554476#26556540. The second approach avoids Rails UJS in favour of Turbo Frame.

Of course there are even more ways how to do this kind of thing. Keep sharing!


Nice. I did something similar recently with an iFrame where you have a form for configuring customer sites and it displays the preview in an iFrame.

I had to throw in some debouncing and special handling for hidden inputs, as changes to hidden inputs do not trigger a change event on the form.

In the end the markup was very simple and is very reusable (StimulusJS v1):

        <div data-controller="iframe-preview" data-iframe-preview-url="<%= preview_path(@letter) %>">
          <form data-target="iframe-preview.form">
             <textarea name="body">My letter</textarea>
         </form>
         <iframe data-target="iframe-preview.iframe">
        </div>
This is when StimulusJS becomes really nice, when you can compose behavior in your markup with some simple data attributes. I did not think at first that I would need this controller in other places, but a couple of weeks later, I actually needed it, and was able to reuse the controller without modification for another use case.


Nice approach. And yes, Stimulus is great for things like this.


As someone who is only vaguely familiar with Rails and doesnt really know anything about stimulus, it would be nice to have a "What this looks like" section with a gif of what the user sees. Otherwise, I dont know what this article is talking about. Im interested as there is probably some application to my daily work, but a wall of code and words doesnt tell me much very quickly.


I just added it. The preview text will become bold, but the idea is that you can send any kind of HTML to inject.


I think the article would be better if you were to include even a basic template instead of just adding the <strong> tag. Right now, my first impression would be "why not just wrap the content in <strong> in JS instead of making a network request on every keystroke?"

The benefit of being able to reuse the server template logic isn't being demonstrate because of the simplicity of the example.


I was thinking of that, but I think the point is that you do any transformation in Ruby. I focused on how to do that. Thanks for the feedback.


Great job! Now I get it!


It‘s just some ajax logic: Put something in a form, an ajax request will be made an the response is inserted into the dom.

The title is pure clickbait, it‘s nothing more than a simple ajax call. The type of logic you‘ve done 10 years ago with a little bit of jquery.


Please enlighten me what would be your title for the post? The idea is making a reusable Stimulus 2 controller.


good to see Rails dev related posts on HN again:)


This is cool but its not using hotwire (turbo frames / turbo streams)


It does! Stimulus 2 is Hotwire too[0].

Turbo seemed more connected with existing models, and so I chose a Stimulus controller instead. But maybe I just don't know Turbo as much yet.

[0] https://stimulus.hotwire.dev/


The whole idea of Hotwire is to prevent `Rails.ajax` calls. You can easily accomplish this with less code:

1. Replace the `output` target with a Turbo frame. 2. Add a value to the `data-controller` div with a `preview-url`: https://stimulus.hotwire.dev/reference/values 3. Change the `Rails.ajax` call with `let url = URL.new(this.previewUrlValue); url.searchParams.append('body', this.tweetTarget.value; this.outputTarget.src = url.toString();` 4. Change the `preview` action to render HTML with the same frame.

Now you have an automatic refreshing frame with less code.


There are a lot of ways to skin this cat!

You can also have a hidden form submission that does this from the existing controller so you don't need to specify the URL.

For example, you can add a hidden submit button like

  form.submit 'preview', data: { composer_target: 'submit' }, hidden: true
Instead of a stimulus target, you wrap your preview area in a turbo frame

  <turbo-frame id="output">
  ...
  </turbo-frame>
In your (ruby) controller you can re-use the existing controller action:

  def create
    @post = Post.new(post_attributes)
    preview && return if params[:commit] == 'preview'
    ...
  end
Do the turbo junk in a private method for the preview:

  private

  def preview
    render turbo_stream: turbo_stream.replace(
      'output', partial: "posts/preview", locals: { post: @post }
    end
  end

Your stimulus controller now just does this:

  preview() {
    this.submitTarget.click();
  }
I'm not sure which I prefer!


Yep, that's what actually cross my mind first. In the end, I wanted to avoid depending on the model, but it's certainly interesting.


Once the complexity ramps up and the side effects compound, that's when I really appreciate server side rendering. And incidentally, that's exactly when I like to have the whole model!

I just refactored some old jquery stuff to use turbo and stimulus on a really complex form and it turned out well, I think. But below a certain level of complexity, it would have made sense to just do everything directly in stimulus with no AJAX at all.

I know your preview example is contrived (it's tough getting a real-world representative demo into a blog post, so please don't read this as criticism, I don't mean to say it's a bad example and I'm not trying to pick on it!) but really simple use cases are exactly the "sweet spot" for doing everything directly in the browser using Stimulus without having to call back to the server. Only once you start piling in business logic or reshuffling major parts of the display around does it fully pay off, and that's also about when you'll start appreciating having the whole model and the ability to re-use partials (rather than having to pick off a few pieces manually).

EDIT:

And just to be clear, in the sample code I put in above, you don't actually have to use the model. You can always just shove a single parameter into a special partial and go.


Perfect. I will try it, and perhaps update the post with this approach as well. Thank you. I haven't done anything with Turbo yet, although it's my next step. I first just tried to refresh my Stimulus knowledge with the 2.x changes.

But as for the AJAX call, I still think it's quite simple solution that most will instantly understand (and that's a good thing).


> "I will try it, and perhaps update the post with this approach as well."

this would be very useful, as there are not many comparative articles that really help you choose one approach over another. a number of years ago, i went through a couple rounds of turbo vs. ujs vs. websockets vs. custom js vs. something other library i don't remember atm, to try to figure out the best option for the app i was working on. lots of articles talk about the strengths of a library compared to others, but almost none walk through an example (or two) with enough depth to show those differences explicitly.


I included a second example with Turbo Frame that hopefully shows how Turbo Frames work.


I mean technically they are installed but the example is still using UJS and not the functionality provided by the hotwire libs.


Yes, I was only focusing on Stimulus 2, but someone else posted here how to change the example to use a frame.


At least in the non-rails environment, Turbo and Stimulus are two separate libraries and have to be included individually.


I'm happy I don't have to deal with these paradigms day to day anymore:

- Making everything go through a "controller" instead of well designed clients and APIs. Mixing rendering, redirection, ajax handling as well as page serving as well as business logic all into a "controller" is painful. ("Models" and the spaghetti design patterns they come with are even worse)

- PHP style templates that trigger database queries inline, making them difficult and eventually impossible to optimize

- Scattering view code across random places instead of writing view code with its corresponding view logic in the same file/component

- Wiring up functionality to templates by targeting selectors

- Manually setting innerHTML for dynamic functionality

- Dealing with rails "turbo" anything, which is not useful in a real world application, and dangerous (we've had production outages because of trying to use turbolinks)

- Not being able to customize behavior because you're locked into Rails design limitations


You’re probably going to end up doing all those things anyway- just inventing it yourself instead of following a convention


I agree. I love Rails and think that it was beautifully designed for its purpose, but it's absolute hell once you start trying to integrate React and more complex frontend stuff into it through its view system (vs using it as an API and have a separate frontend).


Webpacker was the first time I ever felt comfortable writing React. It just worked for me. So I have to disagree here.


react_on_rails is actually quite nice. What have you tried to build?


What happened with the outage and Turbolinks?


Interested as well.


Does anyone use Live Preview / Live Views in Phoenix, but in production with a B2C product?

I'm a bit skeptical of Rails performance with the method the author details here, given that Ruby is much, much slower than Elixir.

I'm curious if even Elixir folks deploy Live Views in consumer-facing apps. But I can totally see it being used for admin interfaces or internal applications, for sure.


> I'm a bit skeptical of Rails performance with the method the author details here

The method used in the blog post is making an ajax request to the server and then displaying the results in a div. For comparison, GitHub is running Rails and when you type into its search box it's making an ajax request to the server and displaying the results in a div.

Just wanted to bring that up here because this is different than what Live View does or what using Turbo Streams would do to broadcast changes over a websocket connection if you were using Rails.

I don't know of anyone running a large scale app using Live View as a primary focus but I did chat with someone on my podcast about how they used Phoenix and Live View to build https://textdb.dev. That episode is at https://runninginproduction.com/podcast/68-textdb-is-a-simpl.... TextDB trended here on HN a few months ago at https://news.ycombinator.com/item?id=23948234. It handled the HN front page load without issues, but it's also not doing a ton.



I believe both are used for customer facing applications. However, I am not sure about b2c at scale. You could argue that Hey.com is b2c.

In this case, the main advantage is that scaling is stateless, so your application servers just have to be able to deal with more traffic. There is nothing more to it.

As for Phoenix LiveView, it has both advantage and disadvantage of being stateful (something as using Action Cable). You now have to be scaling web sockets, but you save on authenticating the request (the request is lighter).

Even if LiveView is stateful, it's done on the platform which is optimized exactly for this purpose.

In both approaches, you also have to take into account that server latency might be a deal breaker for you.


Some anecdata as a hey.com user myself: It feels like they've optimized a lot for initial load of the main homepage (The "Imbox"). Obviously this is super important for an e-mail app... but once you start trying to load anything older than the most recent ~20 e-mails (Eg scrolling "The Feed"), then the load times can be horrifically slow (>10s, followed by a janky pop-in of HTML suddenly loaded). I'm not sure if this is caused by this technology in particular or if there's some kind of cache miss happening here, but coming from and comparing to gmail - which hey's makers like to dump on regularly - the performance is miserable. Sure, the number of transferred bytes is small and there's not a lot of JS to load - both of which I'm a big fan of, but the actual load time that I feel as a user is pretty bad for these AJAX calls.

Overall I'm a fan of what they're trying to do with Hey.com, but this particular use case feels like a big UX miss to me.


Would be great to see a paragraph or two about how to write tests for this functionality. Testing has been my pain point regarding front end development with Rails. Compelling tech all the same and a great article.


I am heavy on testing as well. I believe that Basecamp guys would just use the Rails system tests. Therefore there is nothing unique about using Stimulus.


Is capybara + headless chrome / selenium no longer a panacea for all integration testing? I've even used it in a React app.


This is exactly what I use for my Rails apps and rails provides its own abstraction and helpers to make setting up Capybara especially painless.

For any seasoned rails developer this is not difficult. Nothing about hotwire changes the testing approach in any meaningful way versus the old style ujs/jquery techniques, so the older system test tutorials and SO questions are just as valid as they were before.


> Testing has been my pain point regarding front end development with Rails

Are there any technologies where UI testing isn't a massive pain?


I've recently found tests for LiveView with Phoenix to be pretty nice. They're a little bit slower than regular tests, but quite a bit faster than Rails with like Capybara or something like Cypress. Though they are less full featured than something like cypress. YMMV


Would be great if you could add a small video/gif of this


Yes, I will add it!


The post is well written but I don't see the point in this when you can achieve the same in 145 bytes of JS (vs 80kb of stimulus) and avoid a trip to the server:

  document.querySelector("input").addEventListener("input", e => { document.querySelector("p").innerHTML = `<strong>${e.target.value}</strong>` });
Reusing the logic could be done if the server was node or through compiling the formatter to wasm. I struggle to see how Hotwire makes things simpler and has comparable speed to a SPA?


You are right, but the point is that you can do a lot of transformations in Ruby. The demo doesn't do it justice, you can be converting the content to Markdown for example.

As for the Stimulus part, the idea is to use Stimulus for everything interactive, so those extra 80kb is for the whole application.


The author just used Stimulus, not Hotwire - if you add Turbo (the main part of Hotwire) you can remove all the ajax calls. See thread below: https://news.ycombinator.com/item?id=26555842 )


Yes, it doesn't use Turbo, but Stimulus is part of Hotwire as well. And actually I do just send a complete HTML over the wire (even if just with AJAX).


Hey, thanks for the article. The goal of Hotwire is to remove Ajax calls and minimize Javascript client coding as much as possible. That starts with Turbo as the foundation and then Stimulus is the next layer up. There's nothing wrong with doing what you did w/ Stimulus, but calling it Hotwire creates unnecessary confusion for those new to Rails+Hotwire. That confusion is what I'm addressing in the grandparent comment.


I included a second example with Turbo Frame, but I am keeping Stimulus since it's what the article is about (and it's in the title). Maybe you can do without it but you have to use some hidden forms or something. I believe Stimulus makes it "clean."

Btw Turbo Stream is kind of cool, but in this particular case, do you think its' really better?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: