LiveView trigger_event function

524 views
Skip to first unread message

Jonatan Männchen

unread,
Feb 11, 2021, 5:34:23 AM2/11/21
to phoenix-core
Hi,

Problem:

When writing nested components, they can communicate via events as long as the events are triggered by the HTML. As soon as the communication has to go directly without HTML, currently the only option is to use send / handle_info back up to the root component and then pass it down again to the child component that should receive the data.

This stands in the way of encapsulating state inside of components which goes against the rationale for components:

Components are a mechanism to compartmentalize state, markup, and events in LiveView.

Proposal:

I would like to propose the following function:

Phoenix.LiveView.trigger_event(socket_or_pid, target \\ nil, event, payload)

It could be called without target to trigger an event on the root live view.

The first parameter is a socket or a pid to allow to send to my own active socket, or asynchronously (like Phoenix.LiveView.send_update/3).

PR:

If there is some agreement on the topic, I would be happy to provide a PR.

Best,
Jony

José Valim

unread,
Feb 11, 2021, 5:36:35 AM2/11/21
to phoeni...@googlegroups.com
I think this is a good idea. We should probably call it send_event and mirror it after send_update. In fact, it may even be a better mechanism to communicate with components. Chris?

--
You received this message because you are subscribed to the Google Groups "phoenix-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to phoenix-core...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/phoenix-core/8538d965-ffc3-4f3b-8a8c-e25a8047c356n%40googlegroups.com.

Jonatan Männchen

unread,
Feb 11, 2021, 5:53:29 AM2/11/21
to phoenix-core
PS: Surface has a mechanism to pass an event as a prop. This is basically a combination of event name / cid. That function could be extended beautifully to take those props as a param.

Chris McCord

unread,
Feb 11, 2021, 9:14:05 AM2/11/21
to phoeni...@googlegroups.com
I'm 👎🏻 on this proposal because handle_event is all about handling untrusted data from the client. Mixing events with trusted data sometimes sent by the server is asking for trouble. Likewise, send_update is already a clean mechanism to communicate component <=> component, for example: `send_update(OtherComponent, id: ..., action: :do_a_thing)`

    def update(%{action: :do_a_thing}, socket) do ...

    def update(assigns, socket) do ...

Jonatan Männchen

unread,
Feb 11, 2021, 9:38:16 AM2/11/21
to phoenix-core
In my opinion there should be a way to communicate the same way up / down the component tree no matter if there's a view or a component in the parent.

With send_update one can only talk to components (given an id), with send / handle_info one can only talk to live views.

This makes it impossible to write a component that can be used in all contexts.

I have to say that I'm getting a bit frustrated using live view. I really enjoyed it when using it for small applications where there's just CRUD cases or similar.
When it gets more complicated it gets massively complex with a wild mix of handle_info / handle_event / update etc. depending if you talk to a form input, a custom component, in which context it is used etc.

We're using Live View for Covid Tracing for regions in Switzerland. We have massively complex data models which have a lot of relations. Those in turn are done via a lot of components.

If we want to take any of those components and make them stateful, we run into this problem: https://groups.google.com/g/phoenix-core/c/ifp-PbUAhAI
Therefore a component can not easily be replaced by a view.

If we replace let's say a native select with a more sophisticated chooser / create component, we loose the ability to use events. We for sure can convert it to use send / handle_info instead, but from then on it only works as a child of a view and no longer as the child of another component.

When building such complex views, encapsulation / communication are really important topics to make the code understandable. Just passing everything up to the root view and then duplicating that code across all views that use said component is just not a solution.

I'm aware that LiveView is a relatively new project and I'm more than willing to help and improve it. But at the moment it seems to me like important proposals are rejected without an alternative solution and without actually looking at the root of the problems that currently exist.

José Valim

unread,
Feb 11, 2021, 9:56:39 AM2/11/21
to phoeni...@googlegroups.com
You sent two proposals and you got feedback for them within minutes. Chris is right, your proposed solution has serious security drawbacks. That's a *no* for the proposed solution. We are not saying *no* to the problem.

If the focus is the problem, then please try writing an issue that focuses on the problem, and not the solution. Assuming the root problem is this:

> In my opinion there should be a way to communicate the same way up / down the component tree no matter if there's a view or a component in the parent.

You have only mentioned it in detail in your last e-mail.

I was writing a reply with another approach on this thread but I will step away until people cool down.

Jonatan Männchen

unread,
Feb 11, 2021, 10:01:40 AM2/11/21
to phoenix-core
Hi Jose,

I'm very interested in hearing your solution.

It is important to note that I on purpose wrote the issue in two parts: The Problem and my Proposal (based on my knowledge at the time)

I'm in no way saying that I have a right to get my proposals accepted or  that my proposals are even good.

Thanks for your time / efforts.

Chris McCord

unread,
Feb 11, 2021, 10:02:30 AM2/11/21
to phoeni...@googlegroups.com
I agree a broader abstraction around communicating up and down a tree remains to be solved, and I've said as much on podcasts and such. We've been waiting to see what kinds of patterns emerge in larger applications before jumping on a solution in particular, so I encourage exploration on this side and feedback from what you're trying in  your app. As José said we are open to having a better story here, but the client event mechanism isn't the best path.

Jonatan Männchen

unread,
Feb 11, 2021, 10:27:56 AM2/11/21
to phoenix-core
This is where the circle closes :)

I've proposed the event trigger as the solution because this is how I solved this particular problem in our project:


The event trigger has allowed us to clean up our code a lot. It is however using private APIs which i would really like to avoid.

I'm happy to explore another (and probably better) solutions if you have any ideas about a better direction.

The project I'm working on is (currently) closed-source. If you're interested I'll provide you with some credentials to check it out.

José Valim

unread,
Feb 11, 2021, 11:05:15 AM2/11/21
to phoeni...@googlegroups.com
The immediate question I have is why you want to use the client mechanism (events) to talk to the component. Are you doing that because that's precisely what you need or that's because that's the only solution you found?

Once again, per your original problem statement, it seems you want to use the same mechanism for client and server communication, which Chris correctly pointed out to be a bad idea. You later mentioned you want a general mechanism for communication up and down. So it is not clear to me what the actual problem is.

So unless I fully grasp what the problem is, I will avoid elaborating, because I don't want to be once again blamed for being dismissed despite answering promptly based on the information I had available.

In any case, thanks for the proposals and making yourself available to work on those.

José Valim

unread,
Feb 11, 2021, 11:15:20 AM2/11/21
to phoeni...@googlegroups.com
s/being dismissed/being dismissive

Apologies for the typo. I will bow out of the discussion for now until I see a proposal I fully grasp the scope of.

Jonatan Männchen

unread,
Feb 11, 2021, 11:21:14 AM2/11/21
to phoenix-core
I used the mechanism because at the time it looked like the way to go. Probably because it has "event" in the name and that seemed to align and also because it is the only method that works both on component / views.

The motivation behind it is to generalize communication as far as possible so that components can be used in different contexts.

I see that differentiating unsafe (external) and safe (internal) calls make sense. I did not think about that at all when I came up with my own solution.

Forget everything written in the thread so far, I'm trying to summarize what I'm trying to accomplish:

This takes one step further back from the problem, therefore the other thread I've opened is also covered in here. I guess it makes no sense to differentiate between the two.

  1. I would like one way to communicate down the tree (parent => child) that works for both live views and components
    1. Only data that is passed on purpose should be passed down (avoid just passing all assigns from parent to child)
    2. Communication happens only over one step (can't pass data directly from grandparent to child, but only grandparent => parent => child)
    3. Changes must trigger either a callback / patch assigns in the child
  2. I would like one way to communicate up the tree (child => parent) that works for both live views and components
    1. Events are communicated up, no automatic change tracking or something like that
    2. Events can include a target (any view / component) to directly communicate from child to grandparent
    3. Such a target can be communicated via the down communication mechanism
  3. For both up / down communication there should be no serialization so that functions / refs / pids etc. can be sent
  4. Those communications should happen inside Erlang and not via Client
Does this make more sense like this?

Cade Ward

unread,
Feb 11, 2021, 4:50:13 PM2/11/21
to phoenix-core
I have also felt these pains. I think the problem exists when making a component reusable:

I have component A. I can not use component A as a child of a View and also nest it inside component B without component A having to know which context it is in. That breaks encapsulation.

José Valim

unread,
Feb 11, 2021, 5:16:15 PM2/11/21
to phoeni...@googlegroups.com
Thanks Cade! That’s a concise statement of the problem. I will think about how to have a unified “message the parent” API.

I think this use case is more important than making LV and LC transparently replaceable.

--
You received this message because you are subscribed to the Google Groups "phoenix-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to phoenix-core...@googlegroups.com.

Jonatan Männchen

unread,
Feb 15, 2021, 6:41:04 AM2/15/21
to phoenix-core
I agree that the unified message the parent is the most important part of this request.

A unified child is less important since the parent contains the child and therefore can also use specific APIs of that child. The other away around is problematic since it may be used in different contexts.

Even with that I still think that it should be possible to pass down data directly into a LV. Is there a reason why it wouldn't be favorable for LV / LC to be replaceable?

Boris Kuznetsov

unread,
Feb 26, 2021, 12:35:41 AM2/26/21
to phoeni...@googlegroups.com
The whole problem of component communication is now new and already solved by many different frameworks.

The LiveView closely resembles SPA architecture only with component state stored on server side instead of client (browser).

React had long time battle with this issue and was solving it by community modules like Redux/MobX until they introduced component called [Contexts](https://reactjs.org/docs/context.html). In Contexts you can define separate objects to store state and include those objects into your component to update it directly as it was your local state.

Vue takes different approach with events (via EventBus) when each component can subscribe and emit to certain events. The code can become a mess quickly if you really need to synchronise the state between many components (different styling depended on light / dark theme).

Simple parent <-> children communication is implemented with callbacks passed from parent to children as options.

I found out that event approach is considered not as good as approach taken by React and community libraries as [Vuex](https://vuex.vuejs.org) exist in Vue ecosystem as well.

Probably, we could look into approach used by Contexts / Vuex to have separate GenServers encapsulating shared state and then make LiveView components work with this state directly.

Reply all
Reply to author
Forward
0 new messages