Programming
Santiago Calvo • 07 JUN 2023
ViewComponent: component-based frontend development at its best
For the last few years, the component-based approach has been king in the realm of front-end app development. Its established popularity lies in the power of components as independent and reusable pieces of code. Probably you’ve already heard of them, especially in relation to User Interface design.
Many front-end frameworks such as React, Swift, Vue, and Angular contributed to the fame of this prevalent pattern. Mostly because it allows developers to turn common UI snippets into reusable software units that streamline the entire development process of an application.
The benefits of components are endless, from time-saving to error avoidance and improved code consistency. Above all, components are loved because they provide an amicable Separation of Concerns, maximizing readability and assuring code maintainability.
In the world of Ruby on Rails, the component programming mindset has just gained a new friend: ViewComponent. We’ll tell you all about it. But let’s go step by step.
What does a component-based approach mean in Ruby on Rails?
As you know by now, at Eagerworks we love to use Ruby on Rails (RoR) as our preferred web framework because it provides us with a lot of cool tools and features to build strong and smarter applications.
Using RoR, there’s a concept that can be compared to components: ERB partials. Partials help you create small pieces of views that can be reused throughout the application. As components, ERB partials are great when you find yourself repeating during the development process. But, as nothing is perfect, partials have their limitations.
The main disadvantage of partials is that they are not performant and therefore can only hold logic inside a partial file. This characteristic makes component readability and maintenance a challenge in case of complex structures.
Here’s when the GitHub community came to the rescue. Inspired by React, they prototyped a new solution to handle views in Ruby on Rails, which made everyone recall the ways of more popular frameworks. This is how ViewComponent came to life.
What are view components and how do they work
ViewComponent is a Ruby gem for creating reusable, testable, and encapsulated view components in RoR. But, what are view components?
If you’re familiar with Angular, this will ring a bell. View components are Ruby objects that make it easy to follow code quality standards. In general, partials and templates with a lot of embedded Ruby make good view components.
Every view component has at least one .rb file with the logic behind the view rendering and an .erb file containing what will be rendered. It would look a lot like this:
# app/components/user_card_component.rb
class UserCardComponent < ViewComponent::Base
def initialize(name:, email:)
super()
@name = name
@email = email
end
end
<!-- # app/components/user_card_component.html.erb -->
<div>
<h1><%= @name %></h1>
<h2><%= @email %></h2>
</div>
# app/views/demo/index.html.erb
<%= render(UserCard.new(name: "John Doe", email: "johndoe@example.com")) %>
As you can see, we have a class file with an initializer that specifies what parameters the view needs. Inside that class file, we can add other methods (for example, helpers) or use them inside the view.
In the end, the view file is not that different from any other view file you might have via a controller. To finish, you can use the view component by calling render and instantiating the class that’s needed.
Another pretty cool feature of ViewComponent is the possibility of adding slots to your components. This may also sound familiar to you if you have played a little bit with React. With it, you can create simple components that would be very helpful when implementing a highly reusable layout.
Let’s see how this works using a table component as an example.
class TableComponent < ViewComponent::Base
renders_one :title # Slot for a single elements
renders_many :headers # Slots that will receive multiple elements
renders_many :rows # Same for this one
def initialize()
super()
end
end
Now we can call these slots on our view to render the table. Just like this:
<div class="flex flex-row justify-between pb-3">
<%= title %>
</div>
<table class="table-bordered-bottom table-sm w-full text-left text-sm">
<thead>
<tr>
<% if headers.present? %>
<% headers.each do |header|%>
<th><%= header %></th>
<% end %>
<% end %>
</tr>
</thead>
<tbody>
<% rows.each do |row| %>
<tr>
<%= row %>
</tr>
<% end %>
</tbody>
</table>
Then, we can use the view components in our views as follows:
<%= render TableComponent.new do |table| %>
<% table.with_title do %>
<div class="flex flex-col">
<h3 class="text-base font-bold"><%= 'Orders' %></h3>
</div>
<% end %>
<% table.with_header { 'Order Status' } %>
<% table.with_header { 'Order Created at' } %>
<% @orders.each do |order| %>
<% table.with_row do %>
<td>
<%= @order.status %>
</td>
<td>
<%= order.created_at %>
</td>
<% end %>
<% end %>
<% end %>
This needs to be said: using slots is slightly wordy compared to more popular front-end JS frameworks. Even so, it still has great ergonomics which makes components reusability a reality.
An additional hint: the slots approach looks nice when paired with Tailwind. Mainly because the lengthy HTML lines derived from defining the classes are hidden behind the component view.
ViewComponent kick-off guideline
In your marks, get set, go! To begin your journey with View Component, you will need to install the pertinent gem by adding the following to your gemfile:
gem "view_component"
Without further ado, you’re ready to start using the generators (Rails’ style!) and quickly creating the view components.
bin/rails generate component UserCard name email
By adding the previous line, you will generate your component (the .rb and .erb files) along with the unit tests. You’ll be able to proceed with its configuration in the .rb file of the application. And that’s it.
As we’ve just seen, getting started is easy. However, there are more ingredients we can add to the mix to make this journey an even more fantastic experience.
How to add superpowers to your view components
View components are simple – just some plain HTML plus the Ruby logic describing how to render the component. However, it is not uncommon to build applications that need a little (or a lot) more than that.
Usually, you’ll need to define the following:
-
How the component looks
-
How the interaction with the component is
When it comes to looks, at Eagerworks we are using more and more TailwindCSS. We love it because it pairs very well with the component framework, making styles descriptive, encapsulated, and reusable.
If you still want to design style sheets or abstract your Tailwind styles to make class names shorter, you can create CSS files and import them. To be honest, this solution could be better. We are still figuring out a better alternative.
View components pair very well with stimulus controllers for interactivity. You can create a controller for each element that requires unique user interaction and later auto import it and register with a nice ESBuild snippet. It would look like this:
import controllers from './*_controller.js';
controllers.forEach((controller) => {
application.register(controller.name, controller.module.default)
});
import componentControllers from '../../components/**/*_controller.js';
componentControllers.forEach((controller) => {
application.register(controller.name, controller.module.default)
});
And change the JSON package to use the following code to execute the JS build command:
"scripts": {
"build": "node esbuild.config.mjs",
"build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify"
}
Let’s be honest. Generating components can look slightly off since you are instantiating the class and calling the render method on top. The differences from the syntax in partials can be a little disappointing. How can you make something more relatable?
module ComponentsHelper
def render_component(component, args = {}, &)
component_class = "#{component}_component".camelize.constantize
render(component_class.new(**args), &)
end
end
This view helper provides a way of calling the component just using its name. For example, you could do something like this to render a table component:
<%= render_component 'table' ... %>
Sidenote: it costs a little bit of performance, so use it at your own discretion.
Is the potential of ViewComponent endless?
We covered the basics here, but there is a lot more to explore from this gem. Here are some other traits of this wonderful framework:
-
Testing. Find nice defaults on how to test component rendering with variants.
-
Previews. These routes were created to make possible the preview of a component. They come in handy when writing a component library or providing an easy way for a design team to review the components. Developers must write how to initialize the preview and what previews are provided. After the creation of the routes, changes to the component are allowed without the need for a complete view.
-
Lifecycle. View components provide lifecycle methods if you need to make a setup in the element before_render.
At Eagerwork, we can’t get enough of ViewComponent. Hope you’ve found this article useful. Keep exploring! The ViewComponent world is amazing. If you need any additional guidance, just give us a shout at eagerworks.com.