Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax for returning multiple turbo streams #77

Closed
ghiculescu opened this issue Jan 9, 2021 · 11 comments
Closed

Syntax for returning multiple turbo streams #77

ghiculescu opened this issue Jan 9, 2021 · 11 comments

Comments

@ghiculescu
Copy link
Contributor

Through trial and error I realised that a controller action can return multiple turbo streams. An example would be if you want to append an item to a list, and also show an alert that the item was created.

The syntax is a bit funky if you do it through a controller:

respond_to do |format|
  format.turbo_stream do
    render turbo_stream: [
      turbo_stream.replace(:flash, partial: "layouts/flash", locals: { notice: "Message posted!" }),
      turbo_stream.append(:messages, partial: "messages/message", locals: { message: message })
    ]
  end
end

(you could also do it through a .turbo_stream.erb file, but I like having this in the controller)

Questions:

  1. Should this be documented? If yes, I can make a PR to add it to https://github.com/hotwired/turbo-site/blob/main/docs/handbook/04_streams.md
  2. Would you be open to a neater syntax? I would love to be able to chain turbo streams, like this:
respond_to do |format|
  format.turbo_stream do
    render turbo_stream: turbo_stream
                              .replace(:flash, partial: "layouts/flash", locals: { notice: "Message posted!" })
                              .append(:messages, partial: "messages/message", locals: { message: message })
  end
end
@dhh
Copy link
Member

dhh commented Jan 9, 2021

While this is possible, it’s discouraged. I’d rather we don’t promote this. You can of course always do whatever you want, but in my style guide, this is much worse than using a template.

@dhh dhh closed this as completed Jan 9, 2021
@miharekar
Copy link

miharekar commented Jan 23, 2021

@dhh One example where this could be useful is when one has a paginated table of items. And lets say you delete one item. Now if you go to the next page, you'll miss one item, because the count is off by one - that one item is now on the previous page.

With multiple streams you could delete the desired item, and append one item that now belongs to this page.

@jeanmartin
Copy link

I'd like to point out that - unless I'm completely mistaken - @dhh's comment refers to multiple page updates from the controller as opposed to using a .turbo_stream.erb template. Of course, updating multiple sections in one response is reasonable, there are many valid use cases.
There seems to be quite a bit of confusion across the web linking to his comment here, claiming dhh is discouraging updating multiple sections.

@SylarRuby
Copy link

I do follow DHH's recommendations both with ruby and php but for this one, come on DHH, cant see no harm with this. Without this, i'd have to refresh the entire page.

@dhh
Copy link
Member

dhh commented May 4, 2022

What @jeanmartin said. By all means include multiple commands in your turbo stream response! It was designed for this. What I don't like is doing that inline in the controller rather than in a template. Just like rendering inline ERB templates in a controller file is discouraged.

@laptopmutia
Copy link

anyone know the syntax so that I could avoid write this ?

render turbo_stream: turbo_stream.prepend("flash", partial: "layouts/flash_messages"), status: :unprocessable_entity

I want to change this turbo_stream.prepend("flash", partial: "layouts/flash_messages") with my *.turbo_stream.erb files

@koenhandekyn
Copy link

what we did - we use view components - but you can do same approach with partials ....

add these helpers in our app controller (or app helper or ...)

  def turbo_stream_update(key, component)
    @turbo_stream_actions ||= []
    @turbo_stream_actions << turbo_stream.update(key, view_context.render(component))
  end

   # more if you want 

  def actions()
    @turbo_stream_actions
  end

and then you can use it like this


        turbo_stream_update("someid", SomeComponent.new(param1: somevalue) )
        turbo_stream_update("otherid", OtherComponent.new(other: othervalue) )
        render turbo_stream: actions

@richjdsmith
Copy link

A couple years later and I came across this thread. In the documentation/codebase I found Module: Turbo::Streams::TurboStreamsTagBuilder which had the following description:

Most turbo streams are rendered either asynchronously via Turbo::Broadcastable/Turbo::StreamsChannel or rendered in templates with the turbo_stream.erb extension. But it’s also possible to render updates inline in controllers, like so...

@dhh , have things changed? Or is creating multiple action.turbo_stream.erb files still the preferred method?

@davidalejandroaguilar
Copy link
Contributor

davidalejandroaguilar commented Oct 12, 2023

I think this syntax (render turbo_stream: [...]) is now broken on 7.1 in system tests (at least with selenium-webdriver 4.14.0).

I'm still digging, but so far, this returns a content length mismatch. When changed to a template or Phlex component, tests pass.

render turbo_stream: [...], content_type: "text/vnd.turbo-stream.html" also doesn't work.

@jaxuk
Copy link

jaxuk commented Dec 7, 2023

I think I just stumbled on your issue @davidalejandroaguilar.

render turbo_stream: [
  turbo_stream.prepend(:target_id, html),
  turbo_stream.remove(:other_target_id)
]

Would give me a content length mismatch when testing with selenium-webdriver adding a .join to array got it sorted.

render turbo_stream: [
  turbo_stream.prepend(:target_id, html),
  turbo_stream.remove(:other_target_id)
].join

Hope that helps you or anyone else having the same issue :D

@dchacke
Copy link

dchacke commented Feb 20, 2024

Following up on DHH’s comment about using turbo-stream templates rather than inline rendering, it’s pretty easy to do:

<%= turbo_stream.replace(:flash, partial: "layouts/flash", locals: { notice: "Message posted!" }) %>
<%= turbo_stream.append(:messages, partial: "messages/message", locals: { message: @message }) %>

Note the change from message to @message.

I’m new to turbo streams but I find turbo-stream templates more convenient and more Rails Way™ than inline turbo streams.

It’s also easier to access helper methods from a template.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests