Ruby-on-Rails ViewComponents tutorial and examples

Posted June 23, 2022 - tagged ruby-on-rails

6 min read

ViewComponent is a Ruby gem for creating reusable, testable, and encapsulated view components in Ruby-on-Rails.

What is a ViewComponent and why should you use them?

A ViewComponent is simply a Ruby object and a template. There are five main reasons why you should use ViewComponents!

  1. Single Responsibility: Instead of view-related logic being scattered across the project, all the logic needed for a template is consolidated into a single class.
  2. Ease of Testing: ViewComponents can be unit tested, unlike traditional Rails templates! Another bonus is that these unit tests are typically more than a hundred times faster than similar controller tests.
  3. Explicit Data Flow: Since ViewComponents use a standard Ruby initializer, the data needed to render the template is explicitly known. This is the opposite of traditional Rails templates, which have an implicit interface.
  4. Improved Performance: On average, ViewComponents are roughly 10 times faster than partials.
  5. Increased Code Quality: Since ViewComponents are just plain old Ruby objects, they're simple to enforce code quality standards on.

ViewComponents are ideal for templates that are constantly reused or would benefit from being tested directly. If you have partials or templates with significant amounts of embedded Ruby, they would probably make good ViewComponents.

A typical use case is the navigation bar, for example in a Bootstrap and Rails app.

Now that you know what ViewComponents are and why you should use them, it's time to implement it yourself!

Create a fresh new Rails app

$> ruby -v  
ruby 3.1.2p20 # Please use Ruby 3! 
$> rails -v  
Rails 7.0.2.4 # And Rails 7 to keep things fresh
$> bundle -v  
Bundler version 2.3.14 # Bundler 2.xx

Now enter your workspace and type

rails new -G myapp # -G so it doesn't initialize a git repository
cd myapp

Now in the gemfile, add

gem "view_component"

Then run this command to complete the setup!

bundle install

Implementing your first ViewComponent

Just like how you can do 'rails generate controller/model', you can also do rails generate component! The component generator accepts a component name then a list of arguments.

bin/rails generate component Example title
# Which will result in
create  app/components/example_component.rb
invoke  test_unit
create    test/components/example_component_test.rb
invoke  erb
create    app/components/example_component.html.erb
# example_component.rb should look like this
class ExampleComponent < ViewComponent::Base
  def initialize(title:)
    @title = title
  end
end
  • Notice how in example_component.rb, ExampleComponent inherits from ViewComponent::Base. To make this more similar to Rails models/controllers, you can make an ApplicationComponent class that inherits from ViewComponent::Base, then have all of your components inherit from ApplicationComponent.
  • Make sure to name components after what they render, NOT the arguments they take in
  • Remember what a ViewComponent is; a Ruby object and a template. Look at how it's implemented: one Ruby file and one corresponding template file with the same base name.
  • Components are held in app/components
  • Component class names should always end in Component

Now, overwrite what's originally in the template file (example_component.html.erb)

echo '<span title="<%= @title %>"><%= content %></span>' > app/components/example_component.html.erb
  • @title is taken from the component class
  • Pay attention to the content accessor, it comes in handy in a moment!

Create a controller:

# inside app/controllers/home_controller.rb  
class HomeController < ApplicationController  
end  

Configure a default route:

# inside config/routes.rb  
Rails.application.routes.draw do  
  get "home/index"  
  root to: "home#index"  
end  

Now create a home folder and a view for home#index

mkdir app/views/home
echo '<%= render(ExampleComponent.new(title: "my title")) do %>' > app/views/home/index.html.erb 
echo '  Hello, World!' >> app/views/home/index.html.erb 
echo '<% end %>' >> app/views/home/index.html.erb 
  • Notice when rendering a component, it's just a regular Ruby initializer and you can just look at the class to see what needs to be passed in. Content passed to the ViewComponent as a block is captured and assigned to the content accessor.

Now, run

bin/rails server

And now you should be able to see the view by pasting http://127.0.0.1:3000 into your browser!

localhost

That's how you render a ViewComponent in a view. You can also render ViewComponents in controllers directly by doing something like

render(ExampleComponent.new(title: "My Title")) { "Hello, World!" }

How do I unit test a component?

Notice how when you generated a component earlier, it created a component test file as well. Paste this inside of `test/components/example_component_test.rb

require "test_helper"

class ExampleComponentTest < ViewComponent::TestCase
  def test_render_component
    render_inline(ExampleComponent.new(title: "my title")) { "Hello, World!" }

    assert_selector("span[title='my title']", text: "Hello, World!")
    # or, to just assert against the text:
    assert_text("Hello, World!")
  end
end

Then simply run

bin/rails test

Wouldn't you look at that, the controller isn't involved at all!

Production-ready Ruby-on-Rails example

Sometimes just reading code is both a great way to learn and a relaxing activity. Let's explore GitHub source code of railsdevs .

here is app/components/conversations/read_indicator_component.html.erb

<% return unless render? %>
<% if show_read? %>
  <div class="flex justify-end my-3 mr-4">
    <div class="flex items-center justify-center space-x-2 text-sm">
      <%= inline_svg_tag "icons/solid/check.svg", class: "h-5 w-5 text-green-600" %>
      <p class="text-gray-500"><%= t(".read") %></p>
    </div>
  </div>
<% else %>
  <div class="flex justify-end my-3 mr-4">
    <div class="flex items-center justify-center space-x-2 text-sm">
      <p class="text-gray-500"><%= t(".unread") %></p>
    </div>
  </div>
<% end %>

And this is app/components/conversations/read_indicator_component.rb

module Conversations
  class ReadIndicatorComponent < ApplicationComponent
    def initialize(user, conversation:)
      @user = user
      @conversation = conversation
    end

    def render?
      @conversation.latest_message&.sender?(@user)
    end

    def show_read?
      @conversation.latest_message_read_by_other_recipient?(@user)
    end
  end
end

Is there anything you find weird ? Google it around, or clone the repo and ransack (locally!) the codebase until you find how things work, it's an excellent way to improve skills.

Conclusion

You can see that all the logic needed to render the component is encapsulated within a single class. It's easy to know exactly what has to be passed into the component for it to be rendered properly as well. And lastly, you are able to do unit tests on ViewComponents which makes it significantly easier to write bug-free code. All of these reasons combined should be more than enough for you to use ViewComponents in all of your Rails projects from now on.

Build your MVP with Rails

👉 We are 2 Ruby-on-Rails lovers. By building BootrAils, we hope we will help you to launch your Startup idea (really) faster.
Take care ! Health first. 🙏
Shino & David.

🎉 Special offer : 10% off for lifetime license
Download