Tips for Using ViewComponents in Rails
One of the concepts that needs to be added in Ruby on Rails is the concept of UI components. We have helpers and partials. However, as the project grows, it becomes very, very messy. Plus, partials are surpassingly slow. š«„
To solve this problem at Angry Building, I'm using ViewComponent gem from Github.
Iām going to cover today
I gave a talk about ViewComponent (slides and video). This post is largely based on this post.
What is ViewComponent?
ViewComponentĀ is a gem that allows you to create UI components encapsulated in Ruby classes. It was extracted fromĀ GithubĀ and is also used byĀ Gitlab.
Here is an example of āFieldsetComponent".
This is how this component is going to be rendered:
<%= render FieldsetComponent.new(fieldset, title: "Bank account") do %>
<%= form.input :bank %>
<%= form.input :iban %>
<%= form.input :bic %>
<% end %>
The component has two partsāa Ruby class and an ERB template
# app/components/fieldset_component.rb
class FieldsetComponent < ViewComponent::Base
attr_reader :title
def initialize(title:)
@title = title
end
end
<!-- app/components/fieldset_component.html.erb -->
<fieldset class="c-box p-4">
<legend class="c-box px-3 py-1">
<%= title %>
</legend>
<div class="space-y-6">
<%= content %>
</div>
</fieldset>
ā¦and that's it. Pretty simple š¤©
TheĀ official siteĀ ofĀ ViewComponentĀ has excellent documentation.Ā
Consider integratingĀ LookbookĀ with ViewComponent. Its setup is just a "gem install" and adding a route.
It can't even be compared to the complexity of integratingĀ PlaybookĀ into aĀ Next.jsĀ project. š
When should I use ViewComponent?
I still use helper and partials; ViewComponent is just a new tool. Here are my rules about how to use it
I use a view component when
I am considering extracting a partial that will be used in 2+ controllers
considering extracting a view helper that generates HTML
have complicated deep nested if-elsif-else (domain in view)
copy a lot of logic around
have to connect with JavaScript
I don't use ViewComponent when:
a partial is only used in one controller (example: _form.html.erb)
view helper, which is a simple pure function (example: format_money)
there is a lot of HTML on one single page - leave it there š¤·āāļø
Tips
Can't name a post "Tips for" if there aren't some actionable tips in the postĀ š§
Have a ācomponentā helper
Have āApplicationComponent
Aliasing slots
Editor enhancements
Lets break those down:
1/ Have a "component" helper.
<%= render FieldsetComponent(title: 'title') %>
// becomes
<%= component :field_set, title: 'title' %>
It is a small change, but it makes components feel more "at home" in Rails' view. Here is a gist of its implementation šĀ link
2/ Have "ApplicationComponent" base class similar to "ApplicationRecord", "ApplicationController".
MineĀ has only two methods, but I still find it useful.
3/ Alias "slots" to make code more readable
ViewComponent has the concept ofĀ slots. It allows you to inject content inside your component. Very useful. However, the slots methods are prefixed with "with_," which I found less readable, so I often hideĀ the factĀ I'm using a slot.
class StatsComponent < ApplicationComponent
renders_many :numbers, StatsNumberComponent
alias number with_number
end
// I find this less readable
<%= component :stats do |c| %>
<% c.with_number :balance %>
<% end %>
// than the alias version
<%= component :stats do |c| %>
<% c.number :balance %>
<% end %>
4/ Have switch to alternative shortcut
I use vim-projectionist and it allows me to define shortcuts to switch between component class and template. Very handy. š
The builder pattern
OneĀ of the more "advanced" patterns I started doing with ViewComponent is the "builder pattern". It is similar to how Rails FormBuilder looks like. You are calling methods of the ViewComponent to define how to display the component.
Here is an example of this pattern for "FilterFormComponent"
<%= component :filter_form, params: params do |form| %>
<% form.search :query %>
<% form.select :user_id, options: @search.user_options %>
<% form.select :source, options: @search.source_options %>
<% form.date_range :date %>
<% form.select :kind, options: @search.kind_options %>
<% end %>
In my talk, I go through even more powerful example of the builder pattern in my TableComponent, where you use the builder pattern to define rules for each cell:
<%= component :table, @search.results do |table| %>
<% table.record :name, :itself %>
<% table.record :apartment %>
<% table.number :document do |record| %>
<% record.documents.each do |document| %>
<%= link_to document.display_number, document_path(document) %>
<% end %>
<% end %>
<% table.record :cashier, :user %>
<% table.date :date %>
<% table.money :cash_reserve_amount %>
<% table.money :wallet_amount %>
<% table.money :total_amount %>
<% table.column :kind do |record| %>
<%= component :transaction_kind_badge, record %>
<% end %>
<% table.actions do |record| %>
<%= button_details transaction_path(record) %>
<% end %>
<% end %>
You can check the code for this š here.
Why not use Phlex?
PhlexĀ is another way to add UI components in Rails applications. It also allows you to put your components in Ruby classes.
The main difference from ViewComponent is that it uses a RubyĀ DSLĀ to define the HTML for the component.Ā
It is awesome, and ifĀ ViewComponentĀ hadn't existed, I would have used it.
However, I prefer to useĀ ViewComponentĀ because
I prefer ERB over the DSL. I don't use components for everything; I only use them for reusable code. Thus, I often move code between ERB and DSL, and converting it from and to the DSL will slow me down. Or if I copied the code from ChatGPT, it will slow me down. Plus, DSL is something else I need to learn.
ViewComponentĀ is backed and used by bothĀ GithubĀ andĀ Gitlab, which gives me confidence in the project's longevity.
Those same reasons might be why someone else will choose Phlex over ViewComponentācontext matters. š
Conclusion
For even more tips and tricks about ViewComponent, my talk isĀ hereĀ with aĀ video. š
Choosing ViewComponent was one of the best architecture decisions I made forĀ ViewComponent. I'll definitely use it again if I'm starting a new Rails project (where Rails renders views).Ā
ViewComponent should be built into Rails, tooĀ bad DHHĀ is not a fan. š¤·āāļø
If you have any questions or comments, you can ping me onĀ Threads,Ā LinkedIn,Ā Mastodon, Twitter or just leave a comment below š