Why Choose ViewComponent over Rails Partials
Tags: rails, viewcomponent
Published: 2024-03-07 20:00:00

ViewComponent, inspired by React, was introduced at [RailsConf 2019](https://www.youtube.com/watch?v=y5Z5a6QdA-M). It gives better data flow, easier to test views and cleaner view code.

There were a few other options before ViewComponent (and few after), but with the backing of GitHub, ViewComponent is my tool of choice (heck, [Rails Designer](https://railsdesigner.com/) is built with it!).

Is it also something you should consider? After all, Rails comes with partials and helpers out-of-the-box. Why add another dependency?

So, first off, the pros and cons of View Component:

**Pros**:

- improved code organization
- performance improvements
- extending and composing

**Cons**:

- another dependency
- over-engineering trap
- learning curve


## Why not use partials and helpers?

Partials and helpers are first-party citizens in any Rails app. Developers know them and have used them for years. I'd say: use them. I default to partials. And only move to a ViewComponent when I need to some more advanced wrangling of data. This is where Rails conventions usually dictate to use helpers, or—more fancy—decorators or presenters.

The biggest issue with helpers is that they are global. That `name`-method you defined in UserHelper is available in all views and partial, not just to _magical_ user object only. Conventions on naming could help here, but that's not ideal.

I do use helpers though! When it’s something I can use throughout my apps I find a place for them. Examples:

- `component "global_hotkeys"`, instead of `render GlobalHotKeysComponent.new`;
- `stream_notification "Saved"`, instead of `turbo_stream.replace "notification" { NotificationComponent.new(message: "Saved") }`;
- or something like a global date/time formatting, eg. `custom_format(user.created_at)`.

Pro-tip: some of these helpers come packaged with [Rails Designer](https://railsdesigner.com/docs/view-helpers/).


## Improved performance

ViewComponent are noticeable faster than partials. This boost is primarily attributed to the pre-compilation of all ViewComponent templates at application startup, as opposed to the runtime (like partials). The improvements are most notable with lots of embedded Ruby.

ViewComponent [claim](https://viewcomponent.org/#performance) they are ~10× faster than partials in real-world use-cases. [Testing components](https://viewcomponent.org/guide/testing.html) are quicker too (or maybe testing traditional Rails views are slow!).

It must be said that if you run a small to medium-sized Rails app, this doesn't apply to you.


## How does ViewComponent work?

Typically components live in `app/components` and look like this (examples taking from the ViewComponent docs):

```ruby
class MessageComponent < ViewComponent::Base
  erb_template <<-ERB
    <h1>Hello, <%= @name %>!</h1>
  ERB

  def initialize(name:)
    @name = name
  end
end
```

And are instantiated like so:

```ruby
<⁠%= render(MessageComponent.new(name: "World"))
```

A test could then look like this:

```ruby
require "test_helper"

class MessageComponentTest < ViewComponent::TestCase
  def test_render_component
    render_inline(ExampleComponent.new(name: "Hello, World!"))

    assert_text("Hello, World!")
  end
end
```


## More advanced UI components

As ViewComponent are simply just plain Ruby objects, you can extend them or use composition. Making your components more reusable and dry up some code. Next to you can use “slots”, ”collections” and “conditional rendering”.

### Slots

I've come to really use slots quite often. Once you know when to use them, you see ways to use them all the time (if you checked the components from [Rails Designer](https://railsdesigner.com/) you know what I mean).

Some examples:

- PageHeadingComponent; with optional page actions (think: “Create” and “View”)
- ModalComponent, optional heading element

ViewComponent comes with two flavors: `renders_one` and `renders_many`. Take a look at the following example:

```ruby
# blog_component.rb
class BlogComponent < ViewComponent::Base
  renders_one :⁠header
  renders_many :⁠posts
end
```

```ruby
<%# blog_component.html.erb %>
<h1><⁠%= header %></h1>

<⁠% posts.each do |post| %>
  <⁠%= post %>
<⁠% end %>
```

```ruby
<%# index.html.erb %>
<⁠%= render BlogComponent.new do |component| %>
  <⁠% component.with_header do %>
    <⁠%= link_to "My blog", root_path %>
  <⁠% end %>

  <⁠% BlogPost.all.each do |blog_post| %>
    <⁠% component.with_post do %>
      <⁠%= link_to blog_post.name, blog_post.url %>
    <⁠% end %>
  <⁠% end %>
<⁠% end %>
```

This is a very simple example. Take a [look at the docs for more details](https://viewcomponent.org/guide/slots.html).


### Collections

Just like Rails' partials, ViewComponent supports collections too. Take this example:

```ruby
<⁠%= render(ProductComponent.with_collection(@products)) %>
```

```ruby
class ProductComponent < ViewComponent::Base
  def initialize(product:)
    @product = product
  end
end
```

I tend not to use collections all too much. Imagine the `ProductComponent` template like this to go with the above component class:

```ruby
<li>
  <⁠%= @product.name %>
</li>
```

Besides not being valid HTML. I now need to remember to manually wrap the component with `<ul>`-element. Potentially missing some important CSS classes too. No good. I prefer to instead loop over the collection manually inside the component. Keeping things tidy and contained.


### Conditional rendering

This is a feature I often use. Instead of wrapping a partial in a conditional:

```ruby
<⁠% unless Current.user.subscribed? %>
  <⁠%= render partial: "subscribe_form", locals: { user: Current.user} %>
<⁠% end %>
```

You instantiate the component:

```ruby
<⁠%= render SubscribeFormComponent.new(user: Current.user) %>
```

and add the `render?` method in the component class:

```ruby
class SubscribeFormComponent < ViewComponent::Base
  # …
  def render?
    !@user.subscribed?
  end
  #…
end
```

Based on the conditional the component is then rendered or not. This clean up the view quite a bit, don't you think?

These are a few of the upsides to me of using ViewComponent instead of partials. It's not an all-or-nothing situation, most of my Rails apps still use a fair amount of partials. But they need to be simple; no extra view logic needed. I otherwise move it to a ViewComponent.