Now that live.html.heex has gone, how do we live_render/2?

When using live routes this makes sense:

# app.html.heex
<.flash_group flash={@flash} />
<%= @inner_content %>

When using a hybrid approach:

controller
   > show.html.heex
      > live_render(@conn, StuffLive)
         > stuff_live.html.heex

app.html.heex is rendered two times.

While live_flash/2 is still working get_flash/2 has been removed.

When using a hybrid approach one might use:

conn |> put_flash(:info, "oldskool")

and

socket |> put_flash(:info, "hipster")

interchangably. Preferably targeting the exact same html-code.

Maybe someone can help me, right now I’m in a state of total confusion.

Are we supposed to not use live_render/2 ?

Why is flash-code nested inside flash-code while having the exact same DOM-id.
To me it seems having an element id=x means not having another element id=x.

Anyway, frustrating stuff, maybe someone can help, tnx.

If the only reason your controller exists is to call live_render in its template then there is no point and you should just mount the live view in the router.

Otherwise, you’re getting the double render because both the dead view and the live view are using app.html.heex for their layout. There are several different ways to handle this. A couple of ideas:

If you are mostly going to be mounting your LiveViews in dead views, you can set a different layout for live views. In lib/my_app_web.ex there is the live_view helper:

def live_view do
  quote do
    use Phoenix.LiveView,
      layout: {MyAppWeb.Layouts, :app} # Change :app to :live

    unquote(html_helpers())
  end
end

Then create a new layout called live.html.heex.

If you’re only sometimes embedding LiveViews sometimes, then you can always override layout on a case-by-case basis in mount/3’s return tuple:

def mount(_params, _session, socket) do
  {:ok, socket, layout: {MyAppWeb.Layouts, :live}} # I'm actually not 100% what the value looks like here, I've personally never done this yet
end
1 Like

Thank you.

I’m trying to figure out how we are supposed to use the new basic setup.
Which includes flash_groups including fixed DOM-id’s that will be rendered every time a liveview is rendered.
Unless we revert back to the previous “old” basic setup (like you suggest).
I don’t understand the improvements.

Personally I prefer a working “dead” application which I will “spice up” using liveviews.
So I will add interactivity to forms for example but not override “submit”.
I fail to see how I would benefit from mounting such a liveview on the router.

To me it seems that mounting liveviews on the router must result in a tightly coupled mess.
I must be misunderstanding something.

I think there are many issues conflated here. The duplication of flash rendering is probably just a matter of not having disabled the layout configured for use MyAppWeb, :live_view for the live_rendered liveview. You can disable that e.g. by returning {:ok, socket, layout: false} from c:mount/3.

The reason for the new flash api is quite simple. The new api works no matter the context compared to the old api, which needed to differenciate between live and none-live contexts.

Also nothing really changed in regards to using router mounted LVs and live_render. The tradeoffs are still the same.

3 Likes

Thank you.

I like this {:ok, socket, layout: false}.
The flash_groups in app.html.heex will work as intended.
It’s starting to make more sense now.

1 Like

Hello @LostKobrakai

I’m just trying to understand the flash_group component that comes with the latest Live View install.
In core_components.ex we have :

 @doc """
  Shows the flash group with standard titles and content.

  ## Examples

      <.flash_group flash={@flash} />
  """
  attr :flash, :map, required: true, doc: "the map of flash messages"

  def flash_group(assigns) do
    ~H"""
    <.flash kind={:info} title="Success!" flash={@flash} />
    <.flash kind={:error} title="Error!" flash={@flash} />
    <.flash
      id="client-error"
      kind={:error}
      title="We can't find the internet"
      phx-disconnected={show(".phx-client-error #client-error")}
      phx-connected={hide("#client-error")}
      hidden
    >
      Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
    </.flash>

    <.flash
      id="server-error"
      kind={:error}
      title="Something went wrong!"
      phx-disconnected={show(".phx-server-error #server-error")}
      phx-connected={hide("#server-error")}
      hidden
    >
      Hang in there while we get back on track
      <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
    </.flash>
    """
  end

The phx-disconnected and phx-connected attributes reference the following classes and elements

.phx-client-error #client-error .phx-server-error #server-error

Wondering if you or anyone are able to point me to where these are located? I looked in app.css and they’re not present.

I’m trying to modify the look of the flash alerts and my tailwindcss layout for the alert breaks when liveview disconnects and the JS tries to show or hide these classes.

I’m not sure if I’m missing the point all together with the flash group.

PS - I added the question here as it was the only thread I could see that had some relevance so my apologies if this question should go in a new thread.

There’s no css for those. These are just to identify a dom node to apply the JS commands of show and hide to. The .phx-* classes are dynamically applied by the LV js code.

1 Like

image

Above is image of new flash layout if I had a flash message on mount.

Below is the image on disconnection

image

It seems to lose the flex class.

This is the amended flash component code

attr :id, :string, default: "flash", doc: "the optional id of flash container"
  attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
  attr :title, :string, default: nil
  attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
  attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"

  slot :inner_block, doc: "the optional inner block that renders the flash message"

  def flash(assigns) do
    ~H"""
    <div
      :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
      id={@id}
      phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
      role="alert"
      class={[
        "flex fixed top-2 right-2 w-80 sm:w-96 z-50 text-gray-800"
      ]}
      {@rest}
    >
      <div
        :if={@kind == :info}
        class="text-white flex items-center justify-center p-3 stroke-blue-500 bg-blue-600 rounded-l-lg"
      >
        <.icon class="" name="hero-information-circle-mini" class="h-6 w-6" />
      </div>
      <div
        :if={@kind == :error}
        class="text-white flex items-center justify-center p-3 stroke-red-500 bg-red-600 rounded-l-lg"
      >
        <.icon name="hero-shield-exclamation-mini" class="h-6 w-6" />
      </div>
      <div
        :if={@kind == :warning}
        class="text-white flex items-center justify-center p-3 stroke-yellow-500 bg-yellow-600 rounded-l-lg"
      >
        <.icon name="hero-exclamation-triangle-mini" class="h-6 w-6" />
      </div>
      <div
        :if={@kind == :success}
        class="text-white flex items-center justify-center p-3 stroke-primary-500 bg-primary-600 rounded-l-lg"
      >
        <.icon name="hero-exclamation-triangle-mini" class="h-6 w-6" />
      </div>

      <div class="p-2 border-l-2 border-white bg-white w-full">
        <p
          :if={@title}
          class={[
            "flex items-center gap-1.5 text-sm font-semibold leading-6",
            @kind == :info && "text-blue-600",
            @kind == :error && "text-red-600",
            @kind == :warning && "text-yellow-600",
            @kind == :success && "text-primary-600"
          ]}
        >
          <%= @title %>
        </p>
        <p class="mt-2 text-sm leading-5 text-gray-800"><%= msg %></p>
        <button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
          <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
        </button>
      </div>
    </div>
    """
  end

and the amended flash group code

attr :flash, :map, required: true, doc: "the map of flash messages"

  def flash_group(assigns) do
    ~H"""
    <.flash kind={:info} title="Info!" flash={@flash} />
    <.flash kind={:error} title="Error!" flash={@flash} />
    <.flash kind={:warning} title="Warning!" flash={@flash} />
    <.flash kind={:success} title="Success!" flash={@flash} />

    <.flash
      id="client-error"
      kind={:error}
      title="We can't find the internet"
      phx-disconnected={show(".phx-client-error #client-error")}
      phx-connected={hide("#client-error")}
      hidden
    >
      Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
    </.flash>

    <.flash
      id="server-error"
      kind={:error}
      title="Something went wrong!"
      phx-disconnected={show(".phx-server-error #server-error")}
      phx-connected={hide("#server-error")}
      hidden
    >
      Hang in there while we get back on track
      <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
    </.flash>
    """
  end

As you can see - all I’ve done is added the different types of flash alert to the flash group.

I’m really at a loss as to what’s going on.

The flash group is called in app.html.heex and is simple

<main>
  <.flash_group flash={@flash} />
  <%= @inner_content %>
</main>

Not sure at all about what’s going on. Am I doing something stupid here?

Thanks.

https://hexdocs.pm/phoenix_live_view/0.19.5/Phoenix.LiveView.JS.html#show/0

The show command applies display: block by default to make the element show up.

Aah Eureka! Many thanks for that. I really appreciate you taking the time to respond and for the link.

I’ve been playing around with the new “setup”.
It’s awesome. Great stuff.
Putting liveview at the heart of Phoenix is a bold move.
I like it.
Maybe the name should be changed.
In order for newbees to find answers to questions.

To give some more detailed info

LV will generate a root-div that looks like this:

<div 
  id="phx-..." 
  class="phx-connected" 
  data-phx-main="" 
  data-phx-session="..." 
  data-phx-static="..." 
  data-phx-root-id="phx-...">  

On an error, eg disconnect, @class is set to sth like

phx-loading phx-error phx-client-error

So now show(".phx-client-error #client-error") and {show(".phx-server-error #server-error" can select the right flash. (Descendant combinator - CSS: Cascading Style Sheets | MDN)