4 tips when getting started with Hotwire

Joël Quenneville

Hotwire can feel very different when coming from another front end technology such as React. Here are 4 tips to keep in mind when getting started with Hotwire and Rails.

  1. Pretend Hotwire doesn’t exist
  2. Think RESTfully
  3. Use dom_id for consistent ids
  4. Visualize your frames

Code example: Editing character abilities inline

Consider the following example where we want to add inline editing of abilities to a stat sheet for a dungeons and dragons character. The actual diff is quite small (yay Hotwire!) but we can see all 4 tips at play here.

The character show page renders out all of the abilities:

# app/views/characters/show.html.erb
<section>
  <h2>Abilities</h2>
  <%= render @character.abilities %>
</section

The abilities partial displays the ability along with a link to the edit page. We wrap this in a turbo_frame tag.

# app/views/abilities/_ability.html.erb
+ <%= turbo_frame_tag dom_id(ability) do %>
    <dt><%= ability.name.to_s.titleize %></dt>
    <dd>
      <span><%= ability.value %></span>
      <span>(<%= sprintf "%+d", ability.modifier %>)</span>
      <%= link_to "Edit", [:edit, @character, ability] %>
    </dd>
+<% end %>

The edit page displays a form with a number field to edit the value of a given ability. This diff adds a turbo_frame tag around the form to allow it to be pulled in and replace the show content and enable inline edit.

# app/views/abilities/edit.html.erb
<h1>Edit <%= @ability.name %> for <%= @character.name %></h1>

<section>
<p><%= @ability.description %></p>

+<%= turbo_frame_tag dom_id(@ability) do %>
  <%= form_with model: [@character, @ability] do |f| %>
    <%= f.label :value, @ability.name.to_s.titleize %>
    <%= f.number_field :value, min: 1, max: 20 %>
    <%= f.submit %>
  <% end %>
+<% end %>
</section>

Pretend Hotwire doesn’t exist

Instead of taking a Hotwire-first approach, it’s usually best to start by creating regular Rails resources and then layering on Hotwire. This allows us to rely on Rails defaults. It is also a form of progressive enhancement, allowing your interaction to work fine with Cmd+click or when users have JS disabled.

Basically, start by pretending Hotwire doesn’t exist.

With this approach, you start by building normal Rails pages that link and redirect to each other. Then you can use the various tools that Hotwire provides such as turbo-frames to start composing these pieces together to get an experience that feel a lot more interactive. Even an interaction like inline edit is really just composing regular edit and show pages into some other context. You can see that the diff above is just +4 lines!

Think RESTfully

Hotwire works best when composing RESTful resources. Working with standard index/show/new/edit actions means that the various URL helpers, form helpers, and dom_id, will work nicely for you. It helps you slice up you code into more easily reusable chunks.

Note that these RESTful resources don’t need to line up 1:1 with your database. If there’s a thing on your page that you want to interact with via Hotwire (e.g. an inline edit), then it deserves to be its own resource. In the code example above, the Ability class is an in-memory ActiveModel object, reified from a column on the characters table. Having a dedicated resource at the routing and controller layers gives us URLs we can hit to perform CRUD actions on abilities via Hotwire.

Thoughtful decomposition of your UI into partials is crucial when developing with Hotwire because you’re assembling small pieces of HTML in different contexts. By building things RESTfully, you always have a resource that has an associated partial and responds to to_partial_path and can more easily design form partials tailored to that particular resource.

See the classic talk In Relentless Pursuit of Rest to explore this idea further.

Use dom_id for consistent ids

Leaning on dom_id helps keep your ids in sync and avoids a whole class of bugs where you refer to ids in a bunch of places but one place uses underscores and the other uses dashes. In general, you’ll want to use it any time you’re rendering collections of objects. Use this both to name turbo frames, and arbitrary DOM nodes you want to modify via turbo-stream.

If you’re using a custom object that’s not ActiveRecord, you can define the to_key method to make it compatible with this helper.

Step away from code and visualize your frames

Turbo-frames can be be arbitrarily nested and they can be targeted by other frames, links, and forms. It can be hard to get a sense of what frames you have and how they interact with each other when just reading HTML. Switching to a visual medium can help get a better understanding and debug when you’re stuck. This is a variation on the idea that you should stop coding and start drawing.

The simplest way to do this is to take a screenshot of your app and draw some boxes on it. If you want to get fancier, there are some tools such as this browser extension that can automatically detect and higlight frames for you.

Screenshot of a D&D character sheet webpage with turbo frames highlited through the use of a browser extension

Dungeons & dragons & rails

These 4 tips were originally shared as part of my RailsConf talk Dungeons & Dragons & Rails where I demonstrated building an interactive D&D character sheet using Rails and Hotwire.