47
Rendering view components with Turbo Stream broadcasts
View components, via Github’s view_component gem, are growing in popularity in the Rails community but until recently, view components and Turbo Stream broadcasts didn’t play well together. This made using both view components and Turbo Streams in the same application clunky and a little frustrating.
While there were ways to get components working with streams, thanks to a recent addition to turbo-rails, rendering view components from Turbo Streams now works seamlessly out of the box.
To demonstrate how to connect these two powerful tools together, we’ll be building a very simple Rails application that allows users to manage a list of Spies.
Each spy in the list of spies will be rendered using a view component, and when new spies are added to the database, the newly created spy record will be rendered and broadcast via a callback in the spy model.
When we’re finished, it’ll work like this:
Beautiful, I know.
This article assumes that you’re comfortable building Rails applications. You won’t need any previous experience with Turbo Streams or view components to follow along.
If you want to skip right to the end the complete code for this article can be found on Github.
Let’s start building!
We’ll be working from a fresh Rails application. I’m working off of the latest Rails 7 release (alpha2 at the time of this writing) but everything in this tutorial will work fine on Rails 6.1 too.
If you want to follow along, you can run the below commands from your terminal, or you can clone this Github repo and skip ahead to the Building the spy component section.
rails new turbo-view-components -T
cd turbo-view-components
rails g scaffold Spy name:string mission:string
bundle add view_component
rails db:create db:migrate
If you’re using Rails 6.1 instead of 7, you’ll also need to install turbo-rails
manually.
bundle add turbo-rails
rails turbo:install
Whichever route you choose, you’re ready to move on to the next section once you have view_component
and turbo-rails
installed in your application and a Spy
resource created. Once you're setup, start up your server with rails s
and head to http://localhost:3000/spies.
To use view components, we need to create a component to render a spy object. We can create new components with the built-in generator view_component
provides:
rails g component Spy spy
This generator will create both a spy_component.rb
file and a spy_component.html.erb
file in the components
directory.
Since we're building a very simple component, spy_component.rb
is good to go out of the box. For reference, it should look like this:
class SpyComponent < ViewComponent::Base
def initialize(spy:)
@spy = spy
end
end
spy_component.html.erb
contains placeholder content provided by the generator right now, so let’s update it to render information about each spy:
<div id="<%= dom_id @spy %>">
<p>
<strong>Name:</strong>
<%= @spy.name %>
</p>
<p>
<strong>Mission:</strong>
<%= @spy.mission %>
</p>
<p>
<%= link_to "Show this spy", @spy %>
</p>
</div>
This is just regular old erb
, essentially a copy of the default content of _spy.html.erb
generated by the Rails scaffold generator we ran during application setup.
With the spy component in place, we now need to actually use it. To do that, we’ll update the spies index view to use the our new component. Update spies/index.html.erb
like this:
<p id="notice"><%= notice %></p>
<h1>Spy</h1>
<div id="spies">
<%= render(SpyComponent.with_collection(@spies)) %>
</div>
<%= link_to "New spy", new_spy_path %>
Here we’re using collection rendering to loop through each spy
in @spies
and render each with spy_component.html.erb
.
We’ve now got our list of spies rendering using view components — next up we’ll add Turbo Stream broadcasts so that newly created spies are appended to the list automatically.
First, we need to ensure that visitors to the spies index page are subscribed to the appropriate turbo stream channel.
To do this, we can use the turbo_stream_from
helper from turbo-rails. Update spies/index.html.erb
like this:
<!-- Snip -->
<%= turbo_stream_from "spies" %>
<div id="spies">
<%= render(SpyComponent.with_collection(@spies)) %>
</div>
<!-- Snip -->
There are two important pieces here. First, we added the turbo_stream_from
helper, with spies
as the name.
This helper creates a <turbo-cable-stream-source>
in the rendered HTML with a signed-stream-name that looks something like this:
<turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="aLongSecureName"></turbo-cable-stream-source>
Next, the div wrapping the list of spy components has an id of spies
. This id must match the target of the Turbo Stream broadcast, which defaults to the plural name of the model we are broadcasting from. If our wrapper div doesn’t have an id, the broadcast we add next will fail.
With the stream subscription added to the view, the last step is to add a model callback in models/spy.rb
to broadcast newly created spies on the spies
channel.
Update spy.rb
like this:
class Spy < ApplicationRecord
after_create_commit :append_new_record
private
def append_new_record
broadcast_append_to(
'spies',
html: ApplicationController.render(
SpyComponent.new(spy: self)
)
)
end
end
Here we’re using the new html
option added to Turbo Stream broadcast methods to render the SpyComponent instead of rendering a partial.
Note that using ApplicationController.render
to render a view_component isn’t officially sanctioned by the view_component
team.
The view_component
folks are actively discussing an official way to add support for stream broadcasts, which you can track on this issue.
With the model broadcast in place, we’re ready to test our Turbo Stream-enabled view components.
A note for Rails 7 users: If you’re on Rails 7 alpha2 (the latest release at the time of this writing) an issue exists that will prevent the broadcast from working because of a disabled session error. This issue is entirely unrelated to view components but it will break our broadcast all the same.
This issue will be fixed in the next Rails release, but until then you can prevent the issue by updating development.rb with this line:
config.action_controller.silence_disabled_session_errors = true
To see it action, refresh the index page, and then open a new tab to http://localhost:3000/spies/new, create a new spy, and see that the newly created spy is automatically appended to the list of spies automatically.
Today we looked at a technique to render View Components from Turbo Stream broadcasts, leveraging the recently added ability to render html instead of a partial from Turbo Stream broadcasts.
This article’s focus is on Turbo Stream broadcasts + view_component, so we didn’t dive deep into how Turbo Streams work or how to take full advantage of the real power of view_component.
To dig deeper, you might find these resources helpful starting points:
- The view_component documentation is excellent, and worth reviewing in detail to understand more about how you can use view components
- Encapsulating Ruby on Rails views from Github’s blog is a nice introduction to the work behind the ViewComponent library and includes links to see how Github uses ViewComponents in their application
- The Turbo Rails source code. If you’re serious about using Turbo in your Rails application, thoroughly reviewing the source and accompanying comments is highly recommend.
- The Turbo handbook is the best place to start if you’re new to Turbo and want to understand the basics of Streams, Frames, and Drive
That’s all for today. As always, thanks for reading!
47