ViewComponent: My attempt to answer what value it brings
Tags: rails, viewcomponent
Published: 2020-12-24 11:52:33

---
title: ViewComponent: My attempt to answer what value it brings
published: true
description: A brief introduction to ViewComponent: the API, folder structure, values and concerns.
canonical_url: https://bloggie.io/@kinopyo/viewcomponent-my-attempt-to-answer-what-value-it-brings
tags: rails,viewcomponent
//cover_image: https://direct_url_to_image.jpg
---


[ViewComponent](https://github.com/github/view_component) allows you to write reusable and easy-to-test view components in Rails with pure Ruby.

## A bit of history

ViewComponent, formerly advertised as `ActionView::Component`, was first introduced in Railsconf 2019. I got the impression from the talk that it was planned to be merged into Rails. Then this month when I heard the word that it is “[supported natively](https://edgeguides.rubyonrails.org/layouts_and_rendering.html#rendering-objects) in Rails 6.1,” I thought it’d be available if I upgrade to 6.1. Unfortunately, ViewComponent is still [a separated gem](https://github.com/github/view_component) and you need to install it manually.

What "supported natively" means is that we can use the gem in Rails 6.1 without a monkey patch. To be exact, any 3rd-party solutions (or any Ruby objects) that have implemented a `render_in` method can be passed to ActionView render method in Rails 6.1. Check out the [original PR](https://github.com/rails/rails/pull/36388) for more context.

Even it's not included in the default Rails stack, it still has great potential. If you haven’t watched any of the Railsconf videos, one thing you should know is that ViewComponent is initiated by Github developers. They have been using this approach to construct their views. In other words, it’s been dogfooded on a large scale website and with a large amount of developers, so I believe there is a bright future. 🙂

## Installation

If you're on Rails 6.1:

```ruby
# Gemfile
gem "view_component", require: "view_component/engine"
```

...otherwise, you'll need to put [this monkey patch](https://github.com/github/view_component/blob/master/lib/view_component/render_monkey_patch.rb) in your application.

## How to use

### Folder structure
Components are all stored under `app/components/` folder. The generator will create corresponding files based on your template engine (ERB, Haml, Slim) and testing framework (TestUnit/RSpec).

```
bin/rails generate component ModalComponent id
      invoke  test_unit
      create  test/components/modal_component_test.rb
      create  app/components/modal_component.rb
      create  app/components/modal_component.html.erb
```

### Basic form

Say we're creating a modal component, and it accepts an `id` parameter:

```ruby
# app/components/modal_component.rb
class ModalComponent < ViewComponent::Base
  with_content_areas :header, :body
  
  def initialize(id:)
    @id = id
  end
end
```

```erb
# app/components/modal_component.html.erb
<div class="modal" id="<%= @id %>">
  <div class="header"><%= header %></div>
  <div class="body"><%= body %></div>
</div>
```

Render the component in a parent consumer view, specifying the container `id` and content:
```erb
<%= render(ModalComponent.new(id: "login-modal")) do |component| %>
  <% component.with(:header) do %>
    Hello Jane
  <% end %>
  
  <% component.with(:body) do %>
    <p>Have a great day.</p>
  <% end %>
<% end %>
```

Output:
```html
<div class="modal" id="login-modal">
  <div class="header">Hello Jane</div>
  <div class="body"><p>Have a great day.</p></div>
</div>
```

The content area (header and body) serves like a callback that the parent caller can pass in what _content_ to render, and the modal component gets to decide the _markup_. It's similar to the [named slots in Vue](https://vuejs.org/v2/guide/components-slots.html#Named-Slots).

Note that there is a [more advanced Slots API](https://viewcomponent.org/#slots-experimental), currenly in its second iteration, that is supposed to be the successor to `with_content_areas`. But it's still experimental, you need to keep in mind that it's subject to breaking changes. (It happened to me during the time I wrote this post, the code I wrote a few days ago is now deprecated. 😅)

### Sidecar assets

ViewComponent has a concept of _Sidecar assets_ - views and other assets (CSS, JS) can be placed in the same directory to better encapsulate them.

There are two ways to do this.

1)  Keep only the component Ruby file outside of the sidecar directory:
```
app/components
├── ...
├── example_component.rb
├── example_component
|   ├── example_component.css
|   ├── example_component.html.erb
|   └── example_component.js
├── ...
```

2) Keep everything in the same directory:
```
app/components
├── ...
├── example
|   ├── component.rb
|   ├── component.css
|   ├── component.html.erb
|   └── component.js
├── ...
```

The JavaScript file can also be a Stimulus controller. Take a look at [the doc on Sidecar assets (experimental)](https://viewcomponent.org/#sidecar-assets-experimental) to learn how to configure Webpack to compile the sidecar assets.

There are more advanced usages:

- Conditional rendering
- Using helper methods
- Rendering collections
- before_render hook
- Inline component (or template-less component)
- Previews (like ActionMailer preview)
- Power of composition (render another component inside a component)

Be sure to check out the "[building components](https://viewcomponent.org/#building-components)" section in the official doc to see more examples.

ViewComponent is said to be inspired by React, so if you have some experience with React/Vue, you may find it relatively easy to shift the paradigm of how to write your view logic in Ruby. 

## What value it brings

The official site already does a great job of selling its greatness - [**testing, data flow, performance, and standards**](https://viewcomponent.org/#why-should-i-use-components). 

Here are my takeaways.

### On testing

Keeping up the test coverage for every possible path of the view with Capybara feature specs is expensive and impractical. ViewComponent makes it easy to test your view logic. Because it’s so easy and fast, both the physical and mental hurdle is almost gone, I believe more developers will feel encouraged to spec up their view components.

I would even say this will drive better design decisions. When I was playing around writing some tests for an existing application, it provided me the opportunity to scrutinize my view logic with fresh eyes, to question myself, why I had added such logic in the first place, would it be possible to simplify it or remove it all. Because adding a test also functions like "locking in" the feature, I'd like to be certain such logic is needed to justify the effort. I can see it would drive such conversations between developers and designers more and come up with better decisions.

Here is an example from a codebase I worked on, it was a delightful experience of writing the test:

```ruby
# test/components/comment_preview_component_test.rb
require "test_helper"

class CommentPreviewComponentTest < ViewComponent::TestCase
  def test_no_comments
    answer = create(:answer)

    render_inline(CommentPreviewComponent.new(answer: answer))

    assert_link "Leave a comment"
    refute_link "0 Comments"
  end

  def test_one_comment
    answer = create(:answer)
    create(:comment, answer: answer)

    render_inline(CommentPreviewComponent.new(answer: answer))

    assert_link "Comment 1"
  end

  def test_more_comments
    answer = create(:answer)
    create_list(:comment, 2, answer: answer)

    render_inline(CommentPreviewComponent.new(answer: answer))

    assert_link "Comments 2"
  end

  def test_duplicated_commenters_avatars
    answer = create(:answer)
    user = create(:user)
    # setup 2 unique commenters for the answer
    create_list(:comment, 2, answer: answer, user: user)
    create(:comment, answer: answer, user: answer.user)

    render_inline(CommentPreviewComponent.new(answer: answer))

    assert_selector "img", count: 2
    assert_selector "img[alt='#{user.name} avatar']"
    assert_selector "img[alt='#{answer.user.name} avatar']"
  end
end
```

Running the test:

```
bin/rails test test/components/comment_preview_component_test.rb
Finished in 1.153699s, 3.4671 runs/s, 6.0674 assertions/s.
```

### On packaging and distribution

ViewComponent is reusable by design. [Github's Primer ViewComponents repo](https://github.com/primer/view_components/tree/main/app/components/primer) is a good example of how they've packaged their design system ([Primer CSS](https://github.com/primer/css)) into a ViewComponent-powered gem.

As for the sidecar assets feature that allows you to bundle JavaScript and CSS together, it's still experimental, and I don't know how we can test the JavaScript behavior without sacrificing the speed, but I see great potential in it.

Also, if you happen to use [Tailwind CSS](https://tailwindcss.com) and [Alpine.js](https://github.com/alpinejs/alpine/), your HTML markup would work standalone, which makes it much more shareable with ViewComponent. I highly recommend giving these tools a try. 😎

### More modern, more powerful

As React and Vue gain more popularity, more and more developers have built the mental model of thinking in the component way. But what Rails provides by default (partial, helpers, layouts) have their shortcomings. I have more than once talked to myself, oh I wish I could just drop in a React component here for this specific view logic, that'd make the code simpler and more elegant.

Finally, now we get the tool!

(There are other libraries solving the view-layer problems such as [trailblazer/cells](https://github.com/trailblazer/cells), [komposable/komponent](https://github.com/komposable/komponent), etc. I don't have enough experience to make the comparison 😳.)

## Where to begin

If you want to try out ViewComponent in an existing application, here are some starting points:

* Extract UI based components, such as modal, dropdown, etc.
* Extract application based components, such as complex views with many inline ruby variables and conditions

Try finding some good candidates and write some tests to feel the joy 😃.

## One more thing...

Like the main author [@joelhawksley said in one of the Github Issue](https://github.com/github/view_component/issues/206#issuecomment-587563994), ViewComponent probably is not mature enough to be upstreamed yet. It still needs to iterate with the community. In that sense, living in a separated gem makes the feedback loop much faster.

If you were to try it out today, you should be mentally prepared that even though it provides many functionalities, you may need to make more coding decisions at the beginning such as how you want to organize the folder structures, namespaces, naming conventions, where to put certain logics (partial, helper, or ViewComponent object), etc, as real-world code is more complex and sophisticated.

It’d be a good mental exercise, and it’d become easier once we come up with a community best practice. But for now, it’s up to you to make the call. I enjoy this process, but maybe not for everyone. 🙂

---

I can see myself using ViewComponent as the default choice, I've already installed it in two of my active projects. I’ll share more tips once I get more experience.

Hope you find this post useful. See you next time. 👋

## Resources

- [RailsConf 2019 - Rethinking the View Layer with Components by Joel Hawksley - YouTube](https://www.youtube.com/watch?v=y5Z5a6QdA-M)
- [RailsConf 2020 CE - Encapsulating Views by Joel Hawksley](https://www.youtube.com/watch?v=YVYRus_2KZM)
- [Introduce support for 3rd-party component frameworks by joelhawksley · Pull Request #36388 · rails/rails](https://github.com/rails/rails/pull/36388)
- [Github Primer in ViewComponents · github/primer/view_components](https://github.com/primer/view_components/tree/main/app/components/primer)
- [Renaming to ViewComponent, Syntax Changes · Issue #206 · github/view_component](https://github.com/github/view_component/issues/206)

---

*Originally published at https://bloggie.io on December 17, 2020*