How Hotwire Transforms Web Development: A Deep Dive into Turbo Drive and Turbo Frames
So, why Hotwire, Naomi? That’s a great question. I learned Ruby on Rails in the Backend Program when I attended Turing School of Software & Design. I enjoy creating APIs and handling data, solving complex logic to ensure our application works efficiently.
However, I realized after graduating that I wanted to build full-stack web applications and make them visually appealing. So, I tried learning React before having a solid grasp of JavaScript GASP. It wasn’t the best approach since I was skipping over the basics. I didn’t want to deal with the complexity of JavaScript when working on these smaller projects, you know?
I wanted to quickly build my portfolio to showcase to recruiters. Or easily create an interactive, captivating frontend to showcase what I had accomplished with my backend projects. I find comfort in server-side development and wanted to focus on that while seamlessly integrating an interactive frontend…introducing Hotwire!
Hotwire adds interactivity to our applications by sending HTML over the wire, as opposed to JSON over the wire. This means template rendering happens on the server-side, eliminating the complexity of client-side JavaScript.
Hotwire became the default framework with Rails 7 a couple years ago. If you are on Rails 7 and create a new app rails new my_app
, and check out the Gemfile, you’ll see Turbo and Stimulus included.
But Naomi, what are Turbo and Stimulus? I thought we were talking about Hotwire? We are talking about Hotwire. Turbo and Stimulus are the tools that comprise Hotwire. I’ll only be reviewing Turbo in this blog post, specifically Turbo Drive and Turbo Frames. Let’s dive in.
Turbo Drive
Turbo Drive accelerates our application, specifically when clicking on links or submitting a form. But how does it achieve this?
It automatically converts a link click or form submission event into an AJAX (Asynchronous JS and XML) request. Upon receiving the HTML response (instead of a JSON response), it replaces the <body>
of the current page with the <body>
of the response without triggering a full page reload.
This is a performance improvement we get by default — faster page refresh, happier user. No additional configuration is required on our part to implement this behavior.
To practice and understand this concept, I started a Money Tracker application. The examples in this blog post will be from that app. It’s essentially a budgeting app that I’ve wanted to create for a while, but more on that another time. What’s important to know is that there is an Expense object with CRUD functionality.
In this example, I am editing an Expense. If we inspect the page, you can see how few requests were made when I edited an Expense. We didn’t even have to code anything. The resource was successfully updated without refreshing all the page resources, thanks to Turbo Drive.
If we disable Turbo Drive, like so…
<%= link_to "Edit",
edit_expense_path(expense),
class: "btn btn--light",
data: { turbo: false } %>
Then go through the same flow as before — simply editing an Expense — you can see how many requests were made this time, as there was a full page refresh.
Nice! Now we know how beneficial Turbo Drive is in creating interactive, fast apps.
Turbo Frames
Turbo Frames enhances our app with some impressive functionality as well. It allows us to create isolated, dynamic sections within a web page that can be updated independently. So, the web page is split into independent pieces, right? Doesn’t that eliminate the need for full-page reloads since we can update the relevant independent sections? Indeed.
Turbo Frames and Turbo Drive work together to achieve this. Turbo Drive makes these async requests and updates content on the page, right? Well, Turbo Frames extends this functionality to specific sections of the page without touching the rest of the DOM. Again, no need for full page reloads.
To create Turbo Frames, we use the turbo_frame_tag
helper. By wrapping a certain section with the turbo_frame_tag
, we are defining the section that can be independently updated making it possible to refresh only relevant parts of the page.
It’s important that the turbo_frame_tag
is associated with a unique ID since this ID is used to match the content being replaced when requesting an update from the server. Let’s see an example from Money Tracker.
Below is the code for the _expense.html.erb
partial. You can see the code is wrapped in a turbo_frame_tag
with a unique ID of the Expense.
<%= turbo_frame_tag expense do %>
<div class="expense">
<%= link_to "#{expense.label} - $#{expense.amount}",
expense_path(expense) %>
<div class="expense__actions">
<%= button_to "Delete",
expense_path(expense),
method: :delete,
class: "btn btn--light" %>
<%= link_to "Edit",
edit_expense_path(expense),
class: "btn btn--light" %>
</div>
</div>
<% end %>
This is what the page looks like. See how each Expense is a Turbo Frame with a unique ID.
You can see this when we inspect the page as well.
Now we need a Turbo Frame with the same ID to know where we are headed. Turbo Frame expects a frame of the same ID on the target page so we know what to replace the Frame’s content with.
Below is the edit.html.erb
page. See how the form is wrapped by a turbo_frame_tag
with the same ID.
<main class="container">
<div class="header">
<h1>Edit Expense</h1>
</div>
<%= turbo_frame_tag @expense do %>
<%= render "form", expense: @expense %>
<% end %>
</main>
By wrapping the form partial on our edit.html.erb
page with a turbo_frame_tag
of the same ID as our Expense partial, our app now knows what content to replace the Frame with. Now when we click on “Edit”, the form will automatically replace the Expense partial with the Edit view, as seen below. It only replaces that specific Frame and leaves the rest of the page alone.
When we inspect the page, we can see the Frame is replaced with the Edit view.
What happens when we submit the form though? Well, it will update the existing Frame. Let’s say we upgrade our HBO Max Subscription to ad free. After clicking “Update Expense”, the Frame will update with the HTML we receive from our server.
And just like that, we have updated an Expense without refreshing the entire page.
Let’s Wrap It Up
Well, that’s it for now, fellow devs. Now you know how to use Turbo Drive and Turbo Frames — hopefully you found it as fascinating as I did! Pretty simple and straightforward to implement, huh? Although Money Tracker is in its early stages, I’ve enjoyed incorporating these Hotwire tools and bringing life to my application.
Turbo Frames and Turbo Drive work seamlessly together to enhance the user experience. The asynchronous updates to specific sections of the page? That’s the Rails magic we all love.
Shoutout to Alexandre for the Turbo Rails Tutorial — easy to follow along and set a strong foundation with these concepts.
Now it’s time to continue my journey and learn about Turbo Streams! But not until after Ruby Conf!! Happy coding (: