Stateful live component is mounted more than once

I have a stateful live component. Besides the id, it receives another parameter, “products”, from the parent live view.
The parent live view constains a pubsub subscription and an handle_info that changes the products parameter.
Always when products is changed, the live component is re-rendered (as expected), and re-mounted which is not as expected.

The documentation says, a.o. “In stateful components, mount/1 is called only once, when the component is first rendered. For each rendering, the optional preload/1 and update/2 callbacks are called before render/1.”

So, I would expect that update is called with the new products, parameter. But it appears that also mount is called. What am I missing?

Hi @peter-de-boer, one explanation is that your live component is not actually stateful. Can you supply some code showing how you’re using it / setting it up?

My component:

defmodule BwWeb.CartComponent do
  use BwWeb, :live_component

  def mount(socket) do
    IO.puts("CartComponent mount")
    {:ok, assign(socket, show: false)}

  def update(
      id: _id,
      cart_products: cart_products,
      cart_id: cart_id,
      store_id: store_id,
      store_slug: store_slug,
      show_checkout_button: show_checkout_button,
    } = _assigns, socket) do
    {:ok, assign(
      cart_products: cart_products,
      cart_id: cart_id,
      store_id: store_id,
      store_slug: store_slug,
      show_checkout_button: show_checkout_button

The component is called in the live view template:

   <%= live_component(
      id: Ecto.UUID.generate(),
      cart_products: @cart_products,
      cart_id: @cart_id,
      store_slug: @store.slug,
      show_checkout_button: true
    ) %>

In the live view I handle a pubsub notification:

  def handle_info({Bw.Man.Cart, [cart_id, _], _}, socket) do
    cart_products = Bw.Man.Cart.get_cart_products(cart_id,
    {:noreply, assign(socket, cart_products: cart_products)}

Always when this handle_info is executed, the CartComponent is mounted.

You updated one of the parameters, cart_products, so t has to be remounted. If you don’t want the remount, you have to send whatever thing you want to change via send_update

Thanks, I will try that. I guess I just do not understand the documentation as it seems to suggest that these kind of updates are handled by the update function.

This is your issue. This will generate a new UUID on every parent render which basically means you are unmounting the old component and remounting a new component on every render. Impure functions should generally be avoided in the EEX templating calls.


I’d even suggest you to avoid using stateful component until you are very comfortable with state-less components. Remember there is nothing you cannot do with a live view and a bunch of stateless component. Stateful component is only a feature to improve code organization; and you can improve code organization in other ways.

Awesome, that’s it!

(And I thought I was clever by generating some random id…)