mortenlund

mortenlund

LiveView - Live Component - unmount callback

Hi!
I would like to suggest a new callback in the lifecycle of the Live Component which is unmount.

Sometimes it is nice to be able to cleanup subscriptions and such when the component is removed from the page.

An example is a “chat” component I made that subscribes to PubSub.
The component is used in a page with tabs and is only present in one of the tabs.
Being able to unsubscribe from the PubSub in the same way the subscription was made in mount would be very nice.

First Post!

D4no0

D4no0

I think you should be able to use this terminate callback, even though it’s not guaranteed that it will get called (read why in the docs).

The official way to do this is by monitoring the process: Process — Elixir v1.20.2

Most Liked

LostKobrakai

LostKobrakai

How would you decide if a message received for being subscribed to pubsub is meant to be forwarded to the live component? Those messages are in arbitrary shape and don’t include any hint if the source for the subscription might be a child live component or the live view itself. It could even be that both are subscribed to the same pubsub.

LostKobrakai

LostKobrakai

Tbh I think the issue here is that you use a LiveComponent to subscribe to PubSub in the first place. Given LiveComponents are indeed not processes you’re essentially leaking a subscription onto the parent LiveView as a sideeffect, which then materializes by the fact that you cannot clean that up anymore. I agree that this is a limitation to what a LiveComponent can encapsulate, but imo the solution isn’t an unmount callback. Even with that there would still be no handle_info callback as well, so you’re still dependant on the parent LV.

mortenlund

mortenlund

I very much agree on this!

I have another suggestion to allow PubSub to send events directly to the LiveComponent since this is the same way LiveComponent allready gets its message when using send_update.
Then the LiveComponent could be able to use PubSub without involving the parent LiveView.

This could then call update/2 in the same way send_update allready does this :slight_smile:

The way I solve the handle_info in the parent today is to use Phoenix.LiveView.attach_hook/4 function and then use send_update to forward it to the component :slight_smile:
I attach the hook by defining a hook/1 function in my component that is called from the LiveView.mount function :slight_smile:

Last Post!

mortenlund

mortenlund

Well, they can with some slight tricks :slight_smile: :slight_smile:

I currently have a special module like this that handles the subscription and acts as a special dispatcher.
This does not rely on the parent LiveView and the subscription will be cleaned up when the parent LiveView process dies as normal PubSub subscriptions :slight_smile:

defmodule MyLiveComponent.PubSub do
  @moduledoc false

  def subscribe(%Phoenix.LiveView.Socket{assigns: %{myself: myself} = socket, pubsub, topic) do
    if Phoenix.LiveView.connected?(socket) do
      Phoenix.PubSub.subscribe(pubsub, topic, metadata: %{pid: self(), cid: myself})
      socket
    else
      socket
    end
  end

  def subscribe(%Phoenix.LiveView.Socket{} = socket, pubsub, topic) do
    Phoenix.PubSub.subscribe(pubsub, topic)
    socket
  end

  def unsubscribe(pubsub, topic) do
    Phoenix.PubSub.unsubscribe(pubsub, topic)
  end

  def broadcast(pubsub, topic, message) do
    Phoenix.PubSub.broadcast(
      pubsub,
      topic,
      message,
      __MODULE__
    )
  end

  def broadcast_from(pubsub, pid, topic, message) do
    Phoenix.PubSub.broadcast_from(
      pubsub,
      pid,
      topic,
      message,
      __MODULE__
    )
  end

  def local_broadcast(pubsub, topic, message) do
    Phoenix.PubSub.local_broadcast(
      pubsub,
      topic,
      message,
      __MODULE__
    )
  end

  def dispatch(entries, _dispatch_identificator, message) do
    entries
    |> Enum.each(fn
      {_pid, %{cid: cid, pid: pid}} ->
        Phoenix.LiveView.send_update(pid, cid, source: :pubsub, message: message)

      {pid, _} ->
        send(pid, message)
    end)

    :ok
  end
end

And a live component can be something like this:

defmodule MyComponent do
  use MyWeb, :live_component
  import MyWeb.CoreComponents

  @impl true
  def render(assigns) do
   ~H"""
     <%= @message %>
    <.button phx-event="send">Send</.button>
   """
  end

  @impl true
  def update(%{source: :pubsub, message: message}, socket) do
    socket
    |> assign(:message, message)
  end

  @impl true
  def mount(socket) do
    updated_socket = socket
    |> assign(:message, "No message")
    |> MyLiveComponent.PubSub.subscribe(MyPubSub, "topic")

    {:ok, updated_socket}
  end

  @impl true
  def handle_event("send", _params, socket) do
    MyLiveComponent.PubSub.broadcast(MyPubSub, "topic", "We got a message!")
    {noreply, socket}
  end

Where Next?

Popular in Proposals: Ideas Top

alaadahmed
Hi folks, I tried Phoenix 1.7 and it is awesome, but I have small suggestion, which in my opinion will organize files in a better way. ...
New
davydog187
I’ve been enjoying the new Streams API, but I keep needing to work around the lack of access to the underlying items. One issue that cons...
New
derekkraan
I have been using a multi-endpoint setup in my app, just because I think it makes the most sense for a multi-subdomain app. This worked w...
New
sevensidedmarble
All the pieces are there to execute arbitrary JS commands from the server. You can put them on a data property and call them from the cli...
New
dvartic
Would there be interest to implement a link/1 that gives to option to operate on other events? So I implemented a simple link/1 function...
New
Redbaritone
In the current Auth code, the email must change to be valid. This may be true for the two situations the author has in mind: Registering ...
New
ffloyd
The Problem Currently, if I define a struct in the following way: defmodule MyStruct do # Both x and y will have the FIXED values unti...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36820 110
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New

We're in Beta

About us Mission Statement