Why (and when!) I use ViewComponents

November 09, 2024

Today I want to take a break from the election news and write about something that's been on my mind recently, ViewComponents.

This isn't to say Phlex or other component libraries are inherently wrong, they're just not my cup of tea, but if they're more your style use them! Choice is good, and you should use what fits you and your team best.

What is ViewComponent?

ViewComponent is a Ruby gem for Rails applications that allow you to create a Ruby object that builds markup. I've been an early supporter and user of ViewComponents from the early days of the library, and today I want to write about why and when I use ViewComponents and think you should consider it for your application.

Here's a basic component from their docs:

# app/components/message_component.rb
class MessageComponent < ViewComponent::Base
  def initialize(name:)
    @name = name
  end
end
<%# app/components/message_component.html.erb %>
<h1>Hello, <%= @name %>!<h1>

<%# app/views/demo/index.html.erb %>
<%= render(MessageComponent.new(name: "World")) %>

Returns:

<h1>Hello, World!</h1>

This is the most basic example of a component, because it's just a Ruby class, you can pass it an object or just regular arguments. You're able to keep all the logic related to the view in one place that makes it easily testable.

Why use it?

There are six main reasons in my eyes to use View Components.

  • Single Responsibility

  • Easy Testing

  • Simplified Data Flow

  • Performance*

  • Code quality

  • Reusability & Consistency

Single Responsibility:

In a standard Rails application, you might have logic related to view presentation scattered around models, controllers and helpers. Giving ViewComponent the responsibility of handling all the logic related to rendering a given template you're reducing that into one coherent object that can be easier to understand.

Easy Testing:

With ViewComponent you can write very fast unit tests relating to the presentation logic without needing cumbersome System Tests. This lets you leave the system tests or integration tests for controllers to handle end to end things with your unit tests for components handling other permutations of things.

Unit tests for view components in my experience has been 10x faster to run than integration tests, leading to a quicker development and testing cycle leading me to write more tests for my view logic.

Simplified Data Flow:

By having explicit arguments for ViewComponents, that makes it easier to reason about what params you need to pass in making reusability easier in different contexts, not to mention safer, and also helps to reason about the component's dependencies. Compared to standard Rails partials (that don't use Strict Template Locals), you might miss a local variable needed for a partial, leading to confusion or just improper usage of partials in the wrong contexts.

Performance*:

There's an asterisk here because Performance for components is better than Rails partials in many cases, but Phlex or other component libraries may be faster at this point.

Performance is the lowest reason why you should use view components to me. In a real world application with hundreds of components it rarely came up as a concern for us at my last gig, meaning you won't suffer from using components, but you might not see any noticeable performance benefits either.

Code quality:

With ViewComponents you can avoid long methods, deep conditional nesting and other concerns that plague regular ERB templates and partials.

Of course, like anything you can also find yourself with long methods, or deep conditionals with nesting if you're not careful, but in my experience I find it far easier to keep my ViewComponents cleaner than my erb partials without components.

Reusability & Consistency:

I touched on this briefly with the simplified data flow, but I find it cleaner and far more straightforward to reuse components than I do partials.

Let's take a partial, for a Product resource, you'd commonly render a partial _product.html.erb for it. What happens when you need a slightly different variant for it, for say admins? With ViewComponents, pass in the fact you want an admin context, and you can change urls, or whatever to the admin specific urls.

Using ViewComponents can also help keep things consistent across components and places in your application. I couldn't tell you how many times I copied and pasted code, moved it to a new location, tweaked it slightly over time and then when I need to go change what a Product looks like across the app I suddenly have to play hide and seek with that markup before I used ViewComponents.

When to use it?

My thoughts on when and what should be a component vs a partial varies depending on what I'm working on.

In general:

  • Do I need to test this view logic?

  • Will this view be likely to be reused in other contexts?

  • Is this a design component like a button, list item, nav link, accordion, etc?

If so, it probably belongs as a component for consistency, and the aforementioned reasons.

I use partials along side ViewComponent though, for things that need their own isolation. Strict template literals made it so much nicer in the recent past to solve some pain points with not knowing what arguments partials need for themselves.

For example, I've been working on a proof of concept at work for something and recently had a bunch of ERB relating to a search form on one specific page. It is logic that would be isolated to just that page and won't be reused so I used a partial for that part of the view. Partials can be handy to tuck away code that isn't likely to need to be touched, or if it does get touched can be modified in isolation.

Below that, I'm rendering video thumbnails that play a video preview, so I used a ViewComponent for that logic.

You can totally intermix the two options to find what works for you and your team. You don't need to go all in and write components to replace entire views that you'd render from your controller.

For me, I think the perfect mix is:

  1. Design components

  2. App specific components to encapsulate business logic and using design components.

So we'd have a ButtonComponent for rendering buttons, and all their variations, and Avatar or thumbnail components for the design, but then maybe have a ProductThumbnail component that would accept a product as an argument and then render the thumbnail design component.

The official ViewComponent documentation has a document relating to how GitHub uses view components and I agree with it for the most part. https://viewcomponent.org/viewcomponents-at-github.html

The practical bits:

Ah, what would a blog article be without me telling you reasons NOT to use ViewComponent.

If your app is small, you may be able to go far with partials and helpers and the standard views. They are not a gem I instantly add to the project, but instead are a carefully calculated decision I make when building apps and running into the use case for them. ViewComponent's are especially a knife I reach for when I'm working with a team especially and know we are in it for the long haul. If we need consistency across many pages and know there's complicated business logic that would otherwise be messy or too complex in standard ERB views.

Any time you add a gem you undertake the responsibility of needing to carefully understand the nuances of said gem, because one day you may be maintaining a fork of that dependency or find yourself refactoring your usage of the gem if it ever goes unmaintained. While I think GitHub and the greater ViewComponent user ecosystem is not likely to drop maintaining the gem, it's always a risk.

As always, take advice from the internet with a grain of salt. I do not know your application, or the circumstances that surround your business logic. I'm just sharing what I've found works for me based on my experience using hundreds of ViewComponents over the last 5 years.

I hope you enjoyed this post, if you did reach out on Bluesky and tell me what you thought!

Find me here