Rendering view components with Turbo Stream broadcasts
Tags: rails, hotwire, tutorial, ruby
Published: 2021-11-04 14:32:29

View components, via Github’s [view_component gem](https://github.com/github/view_component), are growing in popularity in the Rails community but until recently, view components and [Turbo Stream broadcasts](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb) 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](https://blog.kuda.dev/making-hotwire-play-nice-with-viewcomponent-ckoilssb40msdv9s10v139kgs) to get components working with streams, thanks to a recent addition to [turbo-rails](https://github.com/hotwired/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:

![A screen recording of a user with two open browser windows. In one, a list of spies is shown. In the other, the user is typing into a form to create a new spy. They finish typing, click a button to create a spy, and the browser window the list of spies is updated to include the newly created spy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbg8mx1q5pnan4gpejvj.gif)

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](https://github.com/DavidColby/turbo-view-components/tree/broadcast-spies).

Let’s start building!

## Application setup
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](https://github.com/DavidColby/turbo-view-components) and skip ahead to the [Building the spy component section](#building-the-spy-component).

```
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](http://localhost:3000/spies).

## Building the spy component
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](https://viewcomponent.org/guide/generators.html) `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:

```ruby
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:

```erb
<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:

```erb
<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](https://viewcomponent.org/guide/collections.html) 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.

## Add Turbo Stream broadcasts
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:

```erb
<!-- 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](https://github.com/hotwired/turbo-rails/blob/a0f14b9e4649f60bcf8fa8b82f35e7f460d60177/app/channels/turbo/streams/stream_name.rb) that looks something like this:

```html
<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](https://github.com/hotwired/turbo-rails/blob/ba86832f7f13001793ab917185788df9723666e8/app/models/concerns/turbo/broadcastable.rb#L68) 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:

```ruby
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](https://github.com/hotwired/turbo-rails/pull/245) 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](https://github.com/github/view_component/issues/1106).

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](https://github.com/rails/rails/pull/43427) in the next Rails release, but until then you can prevent the issue by updating development.rb with this line:

```ruby
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](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.

![A screen recording of a user with two open browser windows. In one, a list of spies is shown. In the other, the user is typing into a form to create a new spy. They finish typing, click a button to create a spy, and the browser window the list of spies is updated to include the newly created spy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1khnm0p459g03qo40ww.gif)

## Wrapping up
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](https://viewcomponent.org/) is excellent, and worth reviewing in detail to understand more about how you can use view components
* [Encapsulating Ruby on Rails views](https://github.blog/2020-12-15-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](https://github.com/hotwired/turbo-rails). If you’re serious about using Turbo in your Rails application, thoroughly reviewing the source and accompanying comments is highly recommend.
* The Turbo [handbook](https://turbo.hotwired.dev/handbook/introduction) 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!