Skip to content

Commit

Permalink
Talk about component/live view messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Oct 27, 2023
1 parent 6a3b33e commit 2292af9
Showing 1 changed file with 76 additions and 19 deletions.
95 changes: 76 additions & 19 deletions lib/phoenix_live_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -215,25 +215,6 @@ defmodule Phoenix.LiveComponent do
classDef callback_req fill:#B7ADFF,color:#000,stroke-width:0,text-decoration:underline
```
## Slots
LiveComponent can also receive slots, in the same way as a `Phoenix.Component`:
<.live_component module={MyComponent} id={@data.id} >
<div>Inner content here</div>
</.live_component>
If the LiveComponent defines an `c:update/2`, be sure that the socket it returns
includes the `:inner_block` assign it received.
See [the docs](Phoenix.Component.html#module-slots.md) for `Phoenix.Component` for more information.
## Live patches and live redirects
A template rendered inside a component can use `<.link patch={...}>` and
`<.link navigate={...}>`. Patches are always handled by the parent `LiveView`,
as components do not provide `handle_params`.
## Managing state
Now that we have learned how to define and use components, as well as
Expand Down Expand Up @@ -369,6 +350,82 @@ defmodule Phoenix.LiveComponent do
will be invoked, triggering the update or update_many callback, which will
load the most up to date data from the database.
### Unifying LiveView and LiveComponent communication
In the examples above, we have used `send/2` to communicate with LiveView
and `send_update/2` to communicate with components. This introduces a problem:
what if you have a component that may be mounted both inside a LiveView
or another component? Given each uses a different API for exchanging data,
this may seem tricky at first, but an elegant solution is to use anonymous
functions as callbacks. Let's see an example.
In the sections above, we wrote the following code in our `CardComponent`:
```elixir
def handle_event("update_title", %{"title" => title}, socket) do
send self(), {:updated_card, %{socket.assigns.card | title: title}}
{:noreply, socket}
end
```
The issue with this code is that, if CardComponent is mounted inside another
component, it will still message the LiveView. Not only that, this code may
be hard to maintain because the message sent by the component is defined far
away from the LiveView that will receive it.
Instead let's define a callback that will be invoked by CardComponent:
```elixir
def handle_event("update_title", %{"title" => title}, socket) do
socket.assigns.on_card_update(%{socket.assigns.card | title: title})
{:noreply, socket}
end
```
And now when initializing the CardComponent from a LiveView, we may write:
```heex
<.live_component
module={CardComponent}
card={card}
id={card.id}
board_id={@id}
on_card_update={fn card -> send(self(), {:updated_card, card}) end} />
```
If initializing it inside another component, one may write:
```heex
<.live_component
module={CardComponent}
card={card}
id={card.id}
board_id={@id}
on_card_update={fn card -> send_update(@myself, card: card) end} />
```
The major benefit in both cases is that the parent has explicit control
over the messages it will receive.
## Slots
LiveComponent can also receive slots, in the same way as a `Phoenix.Component`:
<.live_component module={MyComponent} id={@data.id} >
<div>Inner content here</div>
</.live_component>
If the LiveComponent defines an `c:update/2`, be sure that the socket it returns
includes the `:inner_block` assign it received.
See [the docs](Phoenix.Component.html#module-slots.md) for `Phoenix.Component` for more information.
## Live patches and live redirects
A template rendered inside a component can use `<.link patch={...}>` and
`<.link navigate={...}>`. Patches are always handled by the parent `LiveView`,
as components do not provide `handle_params`.
## Cost of live components
The internal infrastructure LiveView uses to keep track of live
Expand Down

1 comment on commit 2292af9

@wceolin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really interesting example, thanks for sharing. For some reason, I never thought of using a callback for this but it makes a lot of sense, especially for someone coming from a React background where this pattern is common.

Please sign in to comment.