jtormey

jtormey

Adding a handle_info callback to LiveComponents

Lately I’ve been thinking about how to organize components as a LiveView application grows. One of the pain points I’ve found (for myself and beginners) is communicating over PubSub with LiveComponents. The only way to do so is through the parent LiveView, which works well but introduces boilerplate and some indirection, as the LiveView is required to maintain the PubSub subscriptions, and communicate state changes via component assigns or send_update/2.

It would be nice if LiveComponents could implement a handle_info/2 callback, and had a way to manage their own PubSub subscriptions. Well, I was able to implement just that. This is how the “connected” component itself looks:

defmodule CounterComponent do
  use AppWeb, :connected_component

  def render(assigns) do
    ~H"""
    <div {@connected_attrs}>{@count}</div>
    """
  end

  def on_mount(socket) do
    process_setup = fn ->
      Phoenix.PubSub.subscribe(App.PubSub, "inc_channel")
    end

    {:ok, assign(socket, :count, 0), process_setup}
  end

  def handle_info(:inc, socket) do
    {:noreply, update(socket, :count, &(&1 + 1))}
  end
end

I’ve called it “connected” because the component has its own process, which is connected to it via the parent LiveView process. In the on_mount/1 callback, the component returns an anonymous function which is called in a new process and creates any interprocess connections needed by the component (generally, PubSub subscriptions).

In this example, the component receives :inc messages via inc_channel, which when received increments the :count assign on the component. The advantage of this is that the LiveComponent is now fully encapsulated, including its interactions with PubSub.

You can find a complete example and demo video here: GitHub - jtormey/connected_component_demo: Phoenix LiveView project demoing the ConnectedComponent concept · GitHub

The implementation itself is here (not extensively tested, there may be bugs).

Using these components is transparent to the parent LiveView and even allows for deeply nested components to have their own handle_info/2 callbacks. It just requires an on_mount/1 handler to be called in the parent LiveView, which can be applied for all LiveViews globally by calling it in the live_view/1 macro in your web module (you can see how this works in the demo application).

There is a slight performance cost: an additional process for each component that uses this pattern, and each message that is received is copied one more time than would normally be necessary. Though I think these can be justified.

I’m curious if anyone would find this useful in their own applications. Personally I like the convenience and the API, and wonder if something like this might be a worthwhile addition to LiveView itself.

Most Liked

gushonorato

gushonorato

I’m not sure if I agree with that statement because the LiveComponent state will also need to be stored, though not in a lightweight process, but in the parent LiveView. However, I don’t think that’s the main point of the discussion.

I understand that the initial problem you’re trying to solve is the communication between LiveComponents and PubSub. Since the discussion has shifted towards efficiency, I believe it would be beneficial to avoid creating a separate process to accommodate this feature. Instead, a “message broker” within the LiveView itself could handle this task, eliminating the need to spawn additional processes. This approach is more efficient and avoids introducing extra complexity to the LiveComponents.

jtormey

jtormey

I agree, it would be best if the extra process wasn’t necessary. However I wasn’t able to come up with a solution for automatically brokering messages in a single process, there are too many complications and you end up back where we started with manual message routing.

The extra process removes this complexity entirely, when it receives a message you know exactly the component its meant for, then it’s tagged as such and relayed to it. And this is opt-in, you wouldn’t use this for the majority of your components.

If you have specific ideas on implementing a single-process message broker I would love to hear them!

olivermt

olivermt

A major difference is that passing props to a nested liveview and updating it on-page (outside of messages) is much more clunky.

That said I am not convinced the upside is bigger than the added downside/complexity.

Where Next?

Popular in Discussions Top

vans163
So useless benchmarks aside, Its possible to write a webserver that can serve 300k requests per second (perhaps more with optimizations)....
New
ricklove
I was just introduced to Elixir and Phoenix. I was told about the 2 million websocket test that was done 2 years ago. From my research, t...
New
Ankhers
Just a little information upfront. Generally speaking, if I feel like I need to either break a pipe chain or use an anonymous function in...
New
lorenzo
Hey everone! I created a prototype for my app using Nodejs for the api. But the framework I chose wasnt great (in general theresnt any g...
New
RudManusachi
What configs will make sense to put to runtime.exs? – A bit of how I configure apps: I have generic configs in config/config.exs, dev...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54250 245
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
axelson
Decided against including more info in the title, but the gist is that Plataformatec sponsored projects will continue with the assets bei...
New
sashaafm
Piggy backing a bit on @dvcrn topic BEAM optimization for functions with static return type?, I’ve been trying to understand in a deeper ...
New
AstonJ
It’s been a while since we’ve had a thread like this, so what better way to kick off the year with :003: What does being an Elixir user ...
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
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
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

We're in Beta

About us Mission Statement