Portrait of Christopher

Christopher Bennell

I’m a full-stack web developer specializing in Ruby on Rails and Education Technology. Get in touch.

Default, Dynamic Slot Content in Rails ViewComponents

Have you ever wanted to have a default value for a ViewComponent slot, or to make the default value dynamic?

I have an “alert” component which usually renders an icon based on the alert variant (sucess, error, warning, etc.) I recently had a requirement to conditionally render a different icon (in this case, a spinner) independent of the alert variant. The solution I came up with was to use the polymorphic slots, along with a before_render? check to see if an icon has been declared for that slot.

This solution made rendering a spinner icon very concise; I don’t need to include the entire instantiation of the Ui::Spinner component.

Here’s the component template.

app/components/ui/alert.html.erb
1
2
3
4
5
6
7
8
9
<div class="alert alrt-<%= @type %>" role="alert">
  <span class="alert-icon">
    <%= icon %>
  </span>

  <div class="alert-content">
    <%= content %>
  </div>
</div>

And the component class. If the icon slot has not been explicitly filled, we set it to a dynamic default.

app/components/ui/alert.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module Ui
  class Alert < ApplicationComponent
    renders_one :icon, types: {
      spinner: ->(size: :small) { Ui::Spinner.new(size: size) },
      icon: Ui::Icon
    }

    def initialize(type)
      @type = type
    end

    def before_render
      unless icon?
        set_slot(:icon) do
          render default_icon
        end
      end
    end

    def default_icon
      case @type
      when :error
        Ui::Icon.new("block")
      when :primary
        Ui::Icon.new("info_i")
      when :secondary
        Ui::Icon.new("more_horiz")
      when :success
        Ui::Icon.new("check")
      when :warning
        Ui::Icon.new("priority_high")
      end
    end
  end
end

We can render an alert like so, to have the default icon for the alert variant.

1
2
3
4
<%# Renders the success "check" icon %>
<%= render Ui::Alert.new(:success) do %>
  Content...
<% end %>

Or with a spinner.

1
2
3
4
<%= render Ui::Alert.new(:secondary) do |alert| %>
  <% alert.with_icon_spinner %>
    Content...
<% end %>

Or with an any icon we like, using the awkwardly named .with_icon_icon!

1
2
3
<%= render Ui::Alert.new(:secondary) do |alert| %>
  <% alert.with_icon_icon("search") %>
<% end %>

I love discovering the flexibility and power of ViewComponents. I often feel like I’m just scratching the surface of the capabilities.