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.
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.
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.