Creating your first VIEW_COMPONENT with Rails and Hotwire

16 May, 2022

In this tutorial, I'm going to walk you through how to install and get started with View components in a Ruby on Rails application, using Hotwire and Tailwind CSS. Whether you're a beginner or an intermediate developer, this step-by-step guide will help you integrate View components seamlessly into your Rails app.

Installing ViewComponent

First things first, let's install ViewComponent in our Rails application. Open your terminal and type the following command:

bundle add view_component

And that's it—ViewComponent is now installed. The app I've set up for this tutorial uses Tailwind CSS and serves as an example blog post app. We're going to demonstrate how to create a popover for editing a blog post.

Here's what our Tailwind UI popover will look like. We'll convert this popover into a View component with sections for the header, content, and footer. To achieve this, we'll use slots in View components.

Configuring Tailwind CSS

To ensure Tailwind CSS works seamlessly with our View components, we need to update our Tailwind configuration. Add the following to your tailwind.config.js:

module.exports = {
  purge: ['./app/components/**/*.{html,erb,rb}',],
  // other Tailwind config...
}

Creating the Popover Component

Now, we'll generate our first View component. Run the following command in your terminal:

rails generate component Popover

This command will generate two files: popover_component.rb and popover_component.html.erb.

Creating the Turbo Frame

Let's dive into the code. We'll start by modifying our edit.html.erb to load the View component when editing a blog post. First, we'll add a Turbo frame tag:

<%= turbo_frame_tag :popover do %>
  <!-- Edit form code goes here -->
<% end %>

Turbo frames allow us to load content dynamically without redirecting to a new route.

For those new to Turbo frames, I recommend checking out my Hotwire Modals tutorial for a more detailed explanation.

Next, update the application.html.erb layout to include the Turbo frame tags:

<body>
  <!-- Other layout content... -->
  <%= turbo_frame_tag "popover" %>
</body>

Now, let's update the edit link in our blog post show.html.erb view to use Turbo:

<%= link_to "Edit Post", edit_post_path(@post), data: { turbo_frame: "popover" } %>

Styling the Popover

If you refresh the page and click the edit link, you’ll see the edit form loading within the same page. Next, we'll style this form like our desired popover. Copy the HTML from your Tailwind UI template and paste it into the popover_component.html.erb file:

<div class="relative z-10" aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
  <!-- Background backdrop, show/hide based on slide-over state. -->
  <div class="fixed inset-0"></div>

  <div class="fixed inset-0 overflow-hidden">
    <div class="absolute inset-0 overflow-hidden">
      <div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
        <!--
          Slide-over panel, show/hide based on slide-over state.

          Entering: "transform transition ease-in-out duration-500 sm:duration-700"
            From: "translate-x-full"
            To: "translate-x-0"
          Leaving: "transform transition ease-in-out duration-500 sm:duration-700"
            From: "translate-x-0"
            To: "translate-x-full"
        -->
        <div class="pointer-events-auto w-screen max-w-md">
          <form class="flex h-full flex-col divide-y divide-gray-200 bg-white shadow-xl">
            <div class="h-0 flex-1 overflow-y-auto">
              <div class="bg-indigo-700 py-6 px-4 sm:px-6">
                <div class="flex items-center justify-between">
                  <h2 class="text-lg font-medium text-white" id="slide-over-title"><%= title %></h2>
                  <div class="ml-3 flex h-7 items-center">
                    <button type="button" class="rounded-md bg-indigo-700 text-indigo-200 hover:text-white focus:outline-none focus:ring-2 focus:ring-white">
                      <span class="sr-only">Close panel</span>
                      <!-- Heroicon name: outline/x -->
                      <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
                      </svg>
                    </button>
                  </div>
                </div>
              </div>
              <div class="flex flex-1 flex-col p-6 justify-between">
                <%= body %>
              </div>
            </div>
            <div class="flex flex-shrink-0 justify-end px-4 py-4">
              <%= footer %>
            </div>
          </form>
        </div>
      </div>
    </div>
  </div>
</div>

Using Slots in View Components

To make our component more modular, we’ll use slots for different sections like the header, content, and footer.

First, create an initializer in popover_component.rb:

class PopoverComponent < ViewComponent::Base
  attr_reader :title

  def initialize(title:)
    @title = title
  end
end

Update the popover_component.html.erb to make use of the title passed in:

<%# Simplified example %>
<div class="popover">
  <div class="header"><%= title %></div>
  <div class="content"><%= content %></div>
  <div class="footer"><%= footer %></div>
</div>

Initializing the Component in the View

Now, let's render this component in our edit.html.erb.

<%= turbo_frame_tag :popover do %>
  <%= render(PopoverComponent.new(title: "Edit post")) do |c| %>
    <%= c.body do %>
      content in a slot
    <% end %>

    <%= c.footer do %>
      <button type="button" class="rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Cancel</button>
      <button type="submit" class="ml-4 inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Save post</button>
    <% end %>
  <% end %>
<% end %>

Handling Multiple Slots

We need to modify our component to handle multiple slots. Go back to popover_component.rb and add the following:

class PopoverComponent < ViewComponent::Base
  attr_reader :title

  renders_one :body
  renders_one :footer

  def initialize(title:)
    @title = title
  end
end

Finishing Touches

After setting this up, reload your page and click the edit link. You should see the content and footer rendered correctly within the popover.

For more advanced usage of ViewComponent and integrating it with Hotwire, be sure to check the Hotwire Modals video linked below.

If you followed all these steps, congratulations on successfully creating and integrating your first ViewComponent in a Rails application using Hotwire! If you enjoyed this tutorial and found it helpful, please subscribe for more content.

Discussion (0)

To comment you need to sign up for a free account or sign in.

Get new videos in your inbox weekly!

Be the first to hear about new courses and videos launching. You’ll only receive relevant news, no spam.