Modern Front-End Development With Rails

A Brief Hello to Hotwire

Add JavaScript-Free Effects with the Turbo Library

I’m very excited about these tools, and I discuss them at length in Modern Front-End Development With Rails (on sale now)!

In this post, I’m going to show how I used Turbo to add a couple of effects to the sample app used in the book, with basically no custom JavaScript.

In the book, the code supports a schedule page for a list of concerts at a fictional and frankly ridiculous music festival. The details are not super important for our purposes.

As part of the process of doing a new run-through of the book, I recreated the demo app from scratch. I built a stock Rails 6.1 app, meaning Webpacker was installed. I threw in a few gems (like Simple Form), and then set up the data model with some scaffolding, and brought in the two special pages we discuss in the book.

Concert App Setup in Rails 6.1

I started this process with a basic Rails 6.1 CRUD app, with one special view: a schedule display that shows all the concerts and has some navigation bits. The only JavaScript feature I added was Tailwind support.

Then I tried to think of some features I could build to take advantage of the new Turbo library and see how far I could go without writing any JavaScript.

The general point of Hotwire is that the server should communicate with the client by sending rendered HTML rather than JSON and the server should generally be the source of truth and business logic.

The whole point of the Turbo library is to make it extremely easy to call the server, receive HTML, and insert that HTML into your DOM. This was the classic, original Ajax behavior, but it’s now been augmented with much better tooling.

The first thing I thought would be cool was inline editing. Given a concert display on the schedule, click an Edit button to replace that display inline with the edit form, and then on updating the form, update the page with the newly edited display without reloading the rest of the page.

Following is the Favorite Concerts page showing the inline editing in action:

Making Inline Editing Work with Turbo

Before I started this feature, I made sure that the individual concert display was available in its own Rails partial view. In general, the Turbo stuff is going to go far easier for you if your Rails views are consistently structured and if they use Rails defaults.

I also pre-styled the concert edit form to have roughly the same layout as the display page.

With my app more or less where I wanted it, I installed Turbo:

  • In my Gemfile I replaced turbolinks with turbo-rails, and reran bundle install.
  • I ran yarn add @hotwired/turbo-rails and also removed turbolinks.
  • In the Webpacker application.js file, I deleted the Turbolinks and Rails UJS lines, and replaced them with import { Turbo, cable } from "@hotwired/turbo-rails"

With Turbo installed, I then made the following changes to support inline editing:

I wrapped the entire partial for a concert’s display line in a Turbolinks frame with the new turbo_frame_tag helper. The exact code is:

<%= turbo_frame_tag(dom_id(concert)) do %>`,
<% end %>

I also added the Edit button to that HTML, which was a perfectly normal, nothing-to-see-here Rails link:

<%= link_to(edit_concert_path(concert),
class: "text-lg font-bold border p-2") do %>
<% end %>

I also wrapped the entire concert edit form with the same helper: <%= turbo_frame_tag(dom_id(concert)) do %> and made no other changes.

In the ConcertsController, which was a standard Rails scaffold-generated CRUD controller, I changed the response on a successful update from redirect_to(@concert) to render(partial: "concerts/schedule_concert", locals: {concert: @concert}) – just rendering the partial for a single concert display line.

Why Inline Editing Works With So Few Changes

At this point, the inline edit functionality works. With just those changes.

Let’s talk about why.

A Hotwire Turbo Frame makes the claim that, by default, any link or form submission inside the frame will have a response that contains the same turbo frame — <turbo-frame id="same-id">. Turbo will extract those contents and use them to replace the existing contents of the turbo frame. Any part of the response that is not inside that turbo-frame element is ignored.

So, clicking the Edit button returns a regular old Rails get request for the edit form, which has a header and whatnot, but also contains the form itself, which I wrapped in a Turbo Frame earlier. When the response comes back, the client-side part of Turbo captures it, extracts the Turbo Frame part, and replaces the display with the form.

Similarly, after we click on the Update button, we send off a perfectly normal Rails update request. Since we changed one line in the controller, the request returns the HTML to display one concert, wrapped in a Turbo Frame, and once again, client-side Turbo captures and extracts it.

📝 Note: Technically, I could have re-rendered the entire page, and client-side Turbo would have captured the correct segment and ignored the rest. It’d work, but it’d be worse from a performance perspective.

And that’s it. I’ve written no JavaScript and added virtually no incidental complexity to the code, but I’ve got this cool inline editor almost for free.

If you enjoyed this article, be sure to check out Noel’s book on Web Development with Rails.

While he was waiting for the publishing wheels to turn on that book, Noel wrote another book for The Pragmatic Bookshelf about Tailwind:

Save 35% on either book using promo code hotwire_35 The code is valid through August 31, 2021. Note that promo codes cannot be used on prior purchases.

Noel Rappin is an Engineering Manager II at Root Insurance. He is the author of Modern Front-Front End Development For Rails. Find him @noelrap.