Rails Development

Turbo Frames: My First 'Wow' Moment with Hotwire

I'll never forget the moment Turbo Frames clicked for me. I was building a simple task app, frustrated that every form submission caused a full page reload. Then I added one HTML attribute, refreshed the page, and suddenly my form submissions felt like magic—instant updates with no page refresh.

That was my "wow" moment with Hotwire. In this post, I'll recreate that experience and show you how Turbo Frames can transform your Rails app from feeling clunky to feeling modern.

The Problem: Full Page Reloads

Let's start with a traditional Rails form that feels sluggish:

Before: Traditional Rails Form

Controller:

# app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks = Task.all
    @task = Task.new  # For the new task form
  end

  def create
    @task = Task.new(task_params)

    if @task.save
      redirect_to tasks_path, notice: 'Task created!'
    else
      @tasks = Task.all
      render :index
    end
  end

  private

  def task_params
    params.require(:task).permit(:title, :description)
  end
end

View:

<!-- app/views/tasks/index.html.erb -->
<h1>My Tasks</h1>

<!-- New Task Form -->
<div class="new-task-form">
  <h2>Add New Task</h2>

  <%= form_with model: @task do |form| %>
    <% if @task.errors.any? %>
      <div class="errors">
        <% @task.errors.full_messages.each do |message| %>
          <p><%= message %></p>
        <% end %>
      </div>
    <% end %>

    <div class="field">
      <%= form.label :title %>
      <%= form.text_field :title %>
    </div>

    <div class="field">
      <%= form.label :description %>
      <%= form.text_area :description %>
    </div>

    <%= form.submit "Add Task" %>
  <% end %>
</div>

<!-- Tasks List -->
<div class="tasks-list">
  <% @tasks.each do |task| %>
    <div class="task">
      <h3><%= task.title %></h3>
      <p><%= task.description %></p>
      <p><em>Created: <%= task.created_at.strftime("%B %d, %Y") %></em></p>
    </div>
  <% end %>
</div>

The User Experience Problem

When a user submits this form:
1. Full page reload - Everything flickers and reloads
2. Lost scroll position - User gets bounced back to the top
3. Slow feedback - Server round-trip for the entire page
4. Flash messages disappear - If the user scrolls or navigates
5. Feels dated - Like websites from 2010

The Solution: Turbo Frames

Turbo Frames let you update just part of a page instead of the whole thing. Here's how to transform the experience:

Step 1: Wrap Content in Turbo Frames

Update your view to use Turbo Frames:

<!-- app/views/tasks/index.html.erb -->
<h1>My Tasks</h1>

<!-- Wrap the form in a turbo frame -->
<%= turbo_frame_tag "new_task" do %>
  <div class="new-task-form">
    <h2>Add New Task</h2>

    <%= form_with model: @task do |form| %>
      <% if @task.errors.any? %>
        <div class="errors">
          <% @task.errors.full_messages.each do |message| %>
            <p><%= message %></p>
          <% end %>
        </div>
      <% end %>

      <div class="field">
        <%= form.label :title %>
        <%= form.text_field :title %>
      </div>

      <div class="field">
        <%= form.label :description %>
        <%= form.text_area :description %>
      </div>

      <%= form.submit "Add Task" %>
    <% end %>
  </div>
<% end %>

<!-- Wrap the tasks list in another turbo frame -->
<%= turbo_frame_tag "tasks_list" do %>
  <div class="tasks-list">
    <% @tasks.each do |task| %>
      <div class="task">
        <h3><%= task.title %></h3>
        <p><%= task.description %></p>
        <p><em>Created: <%= task.created_at.strftime("%B %d, %Y") %></em></p>
      </div>
    <% end %>
  </div>
<% end %>

Step 2: Update the Controller

Modify your controller to handle Turbo Frame requests:

# app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks = Task.all.order(created_at: :desc)
    @task = Task.new
  end

  def create
    @task = Task.new(task_params)

    if @task.save
      @tasks = Task.all.order(created_at: :desc)

      respond_to do |format|
        format.html { redirect_to tasks_path, notice: 'Task created!' }
        format.turbo_stream do
          render turbo_stream: [
            turbo_stream.update("new_task", partial: "new_task_form", locals: { task: Task.new }),
            turbo_stream.update("tasks_list", partial: "tasks_list", locals: { tasks: @tasks })
          ]
        end
      end
    else
      respond_to do |format|
        format.html do
          @tasks = Task.all.order(created_at: :desc)
          render :index
        end
        format.turbo_stream do
          render turbo_stream: turbo_stream.update("new_task", partial: "new_task_form", locals: { task: @task })
        end
      end
    end
  end

  private

  def task_params
    params.require(:task).permit(:title, :description)
  end
end

Step 3: Create Partials

Extract the form and list into partials for reuse:

<!-- app/views/tasks/_new_task_form.html.erb -->
<div class="new-task-form">
  <h2>Add New Task</h2>

  <%= form_with model: task do |form| %>
    <% if task.errors.any? %>
      <div class="errors">
        <% task.errors.full_messages.each do |message| %>
          <p><%= message %></p>
        <% end %>
      </div>
    <% end %>

    <div class="field">
      <%= form.label :title %>
      <%= form.text_field :title %>
    </div>

    <div class="field">
      <%= form.label :description %>
      <%= form.text_area :description %>
    </div>

    <%= form.submit "Add Task" %>
  <% end %>
</div>
<!-- app/views/tasks/_tasks_list.html.erb -->
<div class="tasks-list">
  <% tasks.each do |task| %>
    <div class="task">
      <h3><%= task
Christopher Lim

Christopher Lim

Rails developer and Unity explorer. Family man, lifelong learner, and builder turning ideas into polished applications. Passionate about quality software development and continuous improvement.

Back to All Posts
Reading time: 4 min read

Enjoyed this rails development post?

Follow me for more insights on rails development, Rails development, and software engineering excellence.