Flash messages with Turbo

A simple controller that redirects to a path with a flash message works in a traditional rails app because usually there is a part in the app layout that renders flash messages.

However, when used with Turbo frames, flash messages are not shown during a frame controlled redirect as they are not part of the returning frame.

To fix this, I’ve wrapped my flash render view in a turbo frame called :notifications and in the action I use a turbo_stream.erb to send 2 streams down the client: one for the actions main response and a second one just to replace the :notifications frame, so the flash messages are shown.

Using this approach shows the flash message at the right time, but also repeats them on the next full refresh.

I was wondering if there is a “correct” way to deal with flash messages in Hotwire which I am completely missing out.

6 Likes

Are you looking for Flash#now?

BTW, you may be interested in this post by Bram Jetten.

Flash messages with Hotwire and Turbo Streams

kBfdG2Tt6A0QRLFf

4 Likes

Thanks for sharing that. This is what I ended up doing, however I’m trying to find an elegant solution when the action needs to stream a partial to the client itself too. In cases like that, the format.turbo_stream can only send one turbo stream down from the action. To send more than one (the main one and the flash one) together, we need to move the payload to a turbo_stream.erb file and include the flash stream there every time.

1 Like

I’m not quite following.

However, when used with Turbo frames, flash messages are not shown during a frame controlled redirect as they are not part of the returning frame.

With a “frame controlled redirect”, are you saying the frame is redirected to additional content, but only display in the frame or it redirects the entire page?

So let’s say you want to redirect to a page after a certain operation is done in your action. In a normal Rails setup, you’d do that with a redirect_to. In such scenario, if you want to show a flash message, you then add a flash in the action too.

The suggested approach can either do the redirect OR the flash, not both. For flash to work with a stream, it would need to render a turbo_stream and a redirect would be considered double rendering.

2 Likes

To clarify, you want to be able to have a Rails controller respond with a turbo-stream (instead of a full-page redirect)? And in this scenario, you are getting the flash rendered within the turbo stream, and then on the next full page request. Is this correct?

If yes, does Flash#now solve the problem?

Another option might be to put a turbo replace in your layout like this:

  <div id="flash_messages">
    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>
  </div>
  <%= turbo_stream.replace "flash_messages" do %>
    <div id="flash_messages">
      <p class="notice"><%= notice %></p>
      <p class="alert"><%= alert %></p>
    </div>
  <% end %>

I think that the stream approach is ok.
An alternative way could be to keep the frame and add a hidden div on the response (or add the same data to an existent element), managed by a Stimulus controller:

<div class='hidden' data-controller='flashMessage' data-message='Record saved!'></div>

The js controller updates/creates the alert at the desired position.

That’s what I was hoping for. When there is a .turbo_stream.erb file involved, one can use render turbo_stream, layout: 'foo' but I guess this is not possible when the rendering is something like this: render turbo_stream: turbo_stream.replace(...) as this doesn’t accept layouts.

The JS approach is interesting as it might even work with a full HTML render which is then inspected by a Stimulus controller for flash frames before it gets back to the original requesting frame.

fwiw the turbo_stream approach also works with a full HTML render.

A strean is a piece of html that have to be inserted/replaced asyncronally in an existing html. A “layout” have not much sense in this scenario. If you need to render more streams with a uniqe turbo_stream.xxxxx call you can always use a partial.

I have the same “problem” and I believe that most people will do to.

I think that we need a solution at the framework level that “sees” flash messages on

turbo responses and do the replace on the “flash_messages” frame.

1 Like

Not exactly.

I think it’s not considered a good practice, but you can do

respond_to do |format|
  format.turbo_stream do
    render turbo_stream: [
      turbo_stream.replace(:flash, partial: "shared/flash", locals: { notice: "My Flash !" }),
      turbo_stream.append(:messages, partial: "messages/message", locals: { message: message })
    ]
  end
end
1 Like

The suggestion in the article doesn’t work because it is for times when we don’t want to redirect the user. In this situation, what OP (and myself) try is to send a flash && redirect.

Sending the flash in a turbo_stream won’t cause a redirection. A form in a turbo_frame tag won’t send the flash to the view.

Hi,

Evil Martian, with well known master Vladimir Dementyev, writings are always rich, in the following article, they talk about flash notification … they setup also a “flash layout for flash turbo_stream” and it’s interesting in my opinion.

Cordialement,

In my case, I need to support both the normal requests and Turbo Stream request. But I really don’t want to change every places I am rendering turbo stream. I ended up creating a small around_action helper which allows me to check if I should broadcast the flash partial to the client.

Prerequisites:

  • a partial for rendering each flash message
  • a container for containing all flash messages

The around_action looks something like this

  def broadcast_flash_message
      return unless request.format.turbo_stream?
      return if response.status == 301 || response.status == 302

      flash.each do |key, message|
        Turbo::StreamsChannel.broadcast_append_to(flash_message_container, target: flash_message_container, partial: "layouts/flash", locals: {message: message, type: key})
      end

      flash.clear
    end

I’m not sure if it’s the correct way to do but it serves me well

4 Likes

is it good practices if I use turbo stream to sent an error notification?

I have done it problem is my turbo stream response have 200 OK instead 422 Error
and whenever I add the 422 Error my browser is not rendering the turbo stream response from server