Live modal not showing flash messages

When I call put_flash on my socket, it shows flash messages in the regular area inside of live.html.heex. It does not show flash messages in the modal when I call put_flash with a modal showing. In the book I’m reading, the modal shows as looking like this:

Have there been some changes that removed flash messages from the default live modal? How can I get mine to look more like the book? I’m using the default .modal component.

 defp handle_progress(:image, entry, socket) do
    if entry.done? do
      path =
        consume_uploaded_entry(
          socket,
          entry,
          &upload_static_file(&1, socket)
        )

      {:noreply,
       socket
       |> put_flash(:info, "file #{entry.client_name} uploaded")
       |> assign(:image_upload, path)}
    else
      {:noreply, socket}
    end
  end
2 Likes

The modal was changed from a LiveComponent to a function component in 1.6.3 and function components don’t use a separate @flash assign on put_flash/3.

Using something like

     {:noreply,
       socket
       |> assign(:flash, %{"info" => "file #{entry.client_name} uploaded"})
       |> assign(:image_upload, path)}

and then retrieving it in the template would probably be the easiest solution.
Under the hood the put_flash/3 / get_flash/2 calls do a similar thing.

1 Like

I’ve ended up here after a few hours of failing to get my flashes to display in the modal.

Your idea gives me the error:
flash is a reserved assign by LiveView and it cannot be set directly. Use the appropriate flash functions instead. Maybe phoenix has changed.

Something like this works in a liveview “controller”

socket =
socket
      |> put_flash(:error, "NONONONON ")
     {:noreply, socket}
# see that has the flash attached

 #  socket: #Phoenix.LiveView.Socket<
#            ...
   # flash: %{"error" => "NONONONON "},
#>

How can I access it? The modal has only assigns and flash is on the socket.

def modal(assigns) do ...

I can’t find a Phoenix.LiveView.get_flash(), but even the regular get_flash needs the socket which the modal does not have. What is get_flash/2 anyway?
The docs now say
This function is deprecated. get_flash/2 is deprecated. Use Phoenix.Flash.get(@flash, key) instead.
but they don’t link to the new version.

Did you include the :fetch_live_flash plug that makes the @flash assign available in your HEEx template?

Note: You must also place the Phoenix.LiveView.Router.fetch_live_flash/2 plug in your browser’s pipeline in place of fetch_flash for LiveView flash messages be supported, for example:

import Phoenix.LiveView.Router

pipeline :browser do
  ...
  plug :fetch_live_flash
end

source: Phoenix.LiveView.put_flash/3 docs

Regarding Phoenix.Flash.get/2, you can see how it’s used in the .flash and .flash_group components from the default core_components.ex file.

Thanks for the response! Yes I’ve already included that plug. That’s not the fix.

This is the modal that comes w/ the liveView generator so I’d assumed they would support flashes. Is there no support for modal flashes? I think the flash might be rendering behind the modal, like on the main page (not 100% sure). Actually, the markup is behind, but no rendering it happening.

Some issues:

  • The form get submitted and goes to handle_event and save_user and the flash message is there. The socket is the full gamut.
  • the template re-renders but the socket is a truncated version w/o all the proper fields.
  • I try to explain below but it’s very hard to :frowning:

#  index.html.heex
# This the the modal with a form inside the modal. The main part of the page.
<.modal return_to={Routes.user_index_path(@socket, ...} > 
   # the form is rendered here
      <.live_component
        module={MyApp.UserLive.FormComponent}
        id={@user.id || :new}~
        title={@page_title}
        action={@live_action}
        user={@user}
        return_to={...}
      />
    </.modal>
 # form_component.ex
# on submit it is sent here
def handle_event("save", %{"user" => user_params}, socket) do
    save_user(socket, socket.assigns.action, user_params)

defp save_user(socket, :new, user_params) do
    case create_user(user_params) do
      ...
     # we're in here now
      {:error, %Ecto.Changeset{} = changeset} ->
        socket =
          socket
          |> put_flash(:error, "TESTTEST123")
          |> assign(:socket, socket)
      # the flash message DOES exist here
        IO.inspect(socket, label: "socket")
     #socket: #Phoenix.LiveView.Socket<
           # id: "phx-F2dreke_buifrTHn",
           # ...
          # flash: %{"error" => "TESTTEST123"},
         # dozens of other fields
        # >

        {:noreply, assign(socket, :changeset, changeset)}
    end
  end

#form_component.html.heex - this is the <.live_component in the modal above
# This should re-render now with the the new socket, but it does not have the full socket. 
<.form
    let={f}
    for={@changeset}
    id="user-form"
    phx-target={@myself}
    phx-change="validate"
    phx-submit="save">
    <%   IO.inspect(@socket, label: "IN FORM") %>
# Phoenix.LiveView.Socket<
#   id: "phx-F2dsD34IwIs3MGZh",
#   ...
#   assigns: #Phoenix.LiveView.Socket.AssignsNotInSocket<>,
#   transport_pid: #PID<0.11640.0>,
# >

The socket picked up by the template is different and missing most of the fields.
I’m making sure to save the socket but for some reason it’s not persisting. Also, the modal doesn’t appear to even be setup for handle flash messages. Those are only on the parent behind.

Since it seems like you’re calling put_flash/3 from within the form LiveComponent and not calling push_navigate/2 or push_patch/2, then that @flash assign by put_flash/3 will only be available within that LiveComponent and will not be available in the parent LiveView meaning the .flash and .flash_group components at the layout level wouldn’t have anything to show.

Also from the put_flash/3 docs:

Note: While you can use put_flash/3 inside a Phoenix.LiveComponent, components have their own @flash assigns. The @flash assign in a component is only copied to its parent LiveView if the component calls push_navigate/2 or push_patch/2.

You should try adding a .flash and/or .flash_group component directly into the .modal component e.g.

<.modal return_to={Routes.user_index_path(@socket, ...} > 
      <.flash_group flash={@flash} />
      <.live_component
        module={MyApp.UserLive.FormComponent}
        ...
      />
    </.modal>

Regarding Phoenix.Flash.get/2, you can see how it’s used in the .flash and .flash_group components from the default core_components.ex file.

I don’t know what U mean by most of this, but I think I know why…

I don’t have a core_components.ex. I found some docs about it but I don’t have any of the files talked about, even though I’ve used the generators. Do U know which generator create those? Googling this hasn’t worked out exactly (to find examples)

I read about those push functions also, but have no immediate idea how they work. I need to push some state from the child (where the flash occurs) to the parent. I guess that is how but at first use it is not working as expected. (Also undefined function push_navigate/2)

The validation messages work as expected, weirdly. After “save” inside the child “controller,” the messages light up inside the form on the same level. But the logs (for testing) inside the form do not fire. So much here I don’t understand hahah.

What version of phoenix are you on, and what version of phoenix did you use to generate your project?

Thanks for the replies.

I managed to get it working. I still don’t fully understand why it works but essentially the modal form was just missing the flash markup. I added in the liveView flash markup and it started to work-ish.


.form
    let={f}
    for={@changeset}
    id="user-form"
    phx-target={@myself}
    phx-change="validate"
    phx-submit="save"> 
<!-- this part I added -->
     <main class="container">
      <p class="alert alert-info" role="alert"
          phx-click="lv:clear-flash"
          phx-value-key="info"><%= live_flash(@flash, :info) %></p>

      <p class="alert alert-danger" role="alert"
           phx-click="lv:clear-flash"
           phx-value-key="error"><%= live_flash(@flash, :error) %></p>
    </main>

   <%= label f, :first_name %>
    <%= text_input f, :first_name %>
    <%= error_tag f, :first_name %>

....
  </.form>