ViewComponents not rendering with Rails stream helper?

Greetings!

I had a quick question about rendering a ViewComponent (or something that responds to render_in) using the Turbo::Streams::TagBuilder helper. From what I can tell, this should be valid code:

respond_to do |format|
    format.turbo_stream do
      render turbo_stream: turbo_stream.prepend(:add_new_user, template: NotificationComponent.new(type: :warning, content: "Email in use"))
    end
end

From my understanding, this should return the NotificationComponent’s markup from render_in as the the content of <template>. Instead I’m seeing the class inspect output when this is prepended: <notificationcomponent:0x00007fe5eca77d60></notificationcomponent:0x00007fe5eca77d60>.

I’ve tried different keywords and for now have settled on creating a partial that renders this Component and using that. My question is am I missing something obvious here that allows you to render a component directly, or is there room to add a new component rendering keyword that can support this behavior in turbo-rails?

Thanks for any feedback or ideas!

This works for ViewComponents:

<%= turbo_stream.update "frame" do %> <%= render Component.new %> <% end %>

That was the solution that I ended up going with for now. I might dabble and see if I can get a more direct way to render it without having to use a template file.

1 Like

I use this way to render ViewComponent in controller without the partial.:

s = turbo_stream.replace dom_id do
  view_context.render(MessageComponent.new(message: @message))
end
render turbo_stream: s
1 Like

Interesting, I think the template file is a better and ‘correct’ solution

Probably so, I’m new to both ViewComponents and Turbo ( obviously :slight_smile: ). I was thinking that, since frames lend themselves to partial content by design, it might be cool to have a more streamlined API for rendering a component. I’m using the inline approach now and it’s working for my purposes.

1 Like

you can use render_to_string

respond_to do |format|
    format.turbo_stream do
      render turbo_stream: turbo_stream.prepend(:add_new_user, render_to_string(NotificationComponent.new(type: :warning, content: "Email in use")))
    end
end

I wrote an article explaining how I made View Components and Hotwire play nicely together.

4 Likes

Sorry to post on an old thread, but struggling with a similar issue, google directed me here.
Thought I’d post my solution:

render(turbo_stream: turbo_stream.replace(dom_id(@activity_feed), Provider::TrophyComponent.new(trophy: @activity_feed).render_in(view_context)))

Indeed, both solutions work for me:

turbo_stream.update('top-pagination', render_to_string(PaginationComponent.new(results: @results)))
turbo_stream.update('top-pagination', PaginationComponent.new(results: @results).render_in(view_context))

The render_in(view_context) feels more “correct”.

What is the best practice for broadcasting a turbo update from inside a model using a ViewComponent? I’ve tried a few different ways but can’t seem to get it to work.