Skip to content

Commit 79990d6

Browse files
ericridgewayGazlerSteffenDEjosevalim
committed
Add doc examples using attach_hook for code organization instead of LiveComponents (#3685)
* Update attach_hook/4 with extract handle_event example * Add small header for doc linking * Clarify the code-organization sections of the Welcome guide Add small hierarchy headers to the "Compartmentalize state, markup, and events" section Use the guidance in "Functional components or live components?" at the top of `Phoenix.LiveComponent` to avoid using LiveComponents only for organization Provide longer example with a few different alternatives of what *can* be used for organizing events * Remove suggested option to delegate events manually As pointed out, sticking with attach_hook keeps ownership of the event in the new component * Remove Helpers and use more-standard {:ok, socket} style * Add forgotten line break * Remove 2nd attach_hook example and link directly to the first * Update lib/phoenix_live_view.ex * Update guides/introduction/welcome.md * Update guides/introduction/welcome.md * Update guides/introduction/welcome.md * Update guides/introduction/welcome.md * Update guides/introduction/welcome.md * Update guides/introduction/welcome.md * Update guides/introduction/welcome.md * Reverse locations of long-code-example & link-to-that-example Now the "using attach_hook/4 to extract handle_events" example code is actually kept in the doc FOR attach_hook This keeps the welcome.md page similar to it's original size * Rename "Organizing code" section -> "Sharing event handling logic" * Continue minimizing changes to welcome.md - Merge the "attach_hook/4 to organize event handling" section into the "Function components" one above it - Return to the previous bullet-point based summary, adding only a single line to emphasize that function components can work alone for markup, but also organize event handling when paired with attach_hook/4 * Apply suggestions from code review * Apply suggestions from code review --------- Co-authored-by: Gary Rennie <gazler@gmail.com> Co-authored-by: Steffen Deusch <steffen@deusch.me> Co-authored-by: José Valim <jose.valim@gmail.com>
1 parent 1628e9a commit 79990d6

File tree

2 files changed

+90
-7
lines changed

2 files changed

+90
-7
lines changed

guides/introduction/welcome.md

+19-6
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ For authentication, with built-in LiveView support, run `mix phx.gen.auth Accoun
229229
LiveView supports two extension mechanisms: function components, provided by
230230
`HEEx` templates, and stateful components, known as LiveComponents.
231231

232+
### Function components to organize markup and event handling
233+
232234
Similar to `render(assigns)` in our LiveView, a function component is any
233235
function that receives an assigns map and returns a `~H` template. For example:
234236

@@ -245,9 +247,16 @@ You can learn more about function components in the `Phoenix.Component`
245247
module. At the end of the day, they are a useful mechanism for code organization
246248
and to reuse markup in your LiveViews.
247249

248-
However, sometimes you need to share more than just markup across LiveViews,
249-
and you also need to move events to a separate module. For these cases, LiveView
250-
provide `Phoenix.LiveComponent`, which are rendered using
250+
Sometimes you need to share more than just markup across LiveViews. When you also
251+
want to move events to a separate module, or use the same event handler in multiple
252+
places, function components can be paired with
253+
[`Phoenix.LiveView.attach_hook/4`](`Phoenix.LiveView.attach_hook/4#sharing-event-handling-logic`).
254+
255+
### Live components to encapsulate additional state
256+
257+
A component will occasionally need control over not only its own events,
258+
but also its own separate state. For these cases, LiveView
259+
provides `Phoenix.LiveComponent`, which are rendered using
251260
[`live_component/1`](`Phoenix.Component.live_component/1`):
252261

253262
```heex
@@ -261,6 +270,11 @@ are more complex than function components themselves. Given they all run in the
261270
same process, errors in components cause the whole view to fail to render.
262271
For a complete rundown, see `Phoenix.LiveComponent`.
263272

273+
When in doubt over [Functional components or live components?](`Phoenix.LiveComponent#functional-components-or-live-components`), default to the former.
274+
Rely on the latter only when you need the additional state.
275+
276+
### live_render/3 to encapsulate state (with error isolation)
277+
264278
Finally, if you want complete isolation between parts of a LiveView, you can
265279
always render a LiveView inside another LiveView by calling
266280
[`live_render/3`](`Phoenix.Component.live_render/3`). This child LiveView
@@ -277,9 +291,8 @@ Given that it runs in its own process, a nested LiveView is an excellent tool
277291
for creating completely isolated UI elements, but it is a slightly expensive
278292
abstraction if all you want is to compartmentalize markup or events (or both).
279293

280-
To sum it up:
281-
282-
* use `Phoenix.Component` for code organization and reusing markup
294+
### Summary
295+
* use `Phoenix.Component` for code organization and reusing markup (optionally with [`attach_hook/4`](`Phoenix.LiveView.attach_hook/4#sharing-event-handling-logic`) for event handling reuse)
283296
* use `Phoenix.LiveComponent` for sharing state, markup, and events between LiveViews
284297
* use nested `Phoenix.LiveView` to compartmentalize state, markup, and events (with error isolation)
285298

lib/phoenix_live_view.ex

+71-1
Original file line numberDiff line numberDiff line change
@@ -1508,7 +1508,77 @@ defmodule Phoenix.LiveView do
15081508
interoperability](js-interop.html#client-hooks-via-phx-hook) because a client hook
15091509
can push an event and receive a reply.
15101510
1511-
## Examples
1511+
## Sharing event handling logic
1512+
1513+
Lifecycle hooks are an excellent way to extract related events out of the parent LiveView and
1514+
into separate modules without resorting unnecessarily to LiveComponents for organization.
1515+
1516+
defmodule DemoLive do
1517+
use Phoenix.LiveView
1518+
1519+
def render(assigns) do
1520+
~H\"""
1521+
<div>
1522+
<div>
1523+
Counter: {@counter}
1524+
<button phx-click="inc">+</button>
1525+
</div>
1526+
1527+
<MySortComponent.display lists={[first_list: @first_list, second_list: @second_list]} />
1528+
</div>
1529+
\"""
1530+
end
1531+
1532+
def mount(_params, _session, socket) do
1533+
first_list = for(i <- 1..9, do: "First List \#{i}") |> Enum.shuffle()
1534+
second_list = for(i <- 1..9, do: "Second List \#{i}") |> Enum.shuffle()
1535+
1536+
socket =
1537+
socket
1538+
|> assign(:counter, 0)
1539+
|> assign(first_list: first_list)
1540+
|> assign(second_list: second_list)
1541+
|> attach_hook(:sort, :handle_event, &MySortComponent.hooked_event/3) # 2) Delegated events
1542+
{:ok, socket}
1543+
end
1544+
1545+
# 1) Normal event
1546+
def handle_event("inc", _params, socket) do
1547+
{:noreply, update(socket, :counter, &(&1 + 1))}
1548+
end
1549+
end
1550+
1551+
defmodule MySortComponent do
1552+
use Phoenix.Component
1553+
1554+
def display(assigns) do
1555+
~H\"""
1556+
<div :for={{key, list} <- @lists}>
1557+
<ul><li :for={item <- list}>{item}</li></ul>
1558+
<button phx-click="shuffle" phx-value-list={key}>Shuffle</button>
1559+
<button phx-click="sort" phx-value-list={key}>Sort</button>
1560+
</div>
1561+
\"""
1562+
end
1563+
1564+
def hooked_event("shuffle", %{"list" => key}, socket) do
1565+
key = String.to_existing_atom(key)
1566+
shuffled = Enum.shuffle(socket.assigns[key])
1567+
1568+
{:halt, assign(socket, key, shuffled)}
1569+
end
1570+
1571+
def hooked_event("sort", %{"list" => key}, socket) do
1572+
key = String.to_existing_atom(key)
1573+
sorted = Enum.sort(socket.assigns[key])
1574+
1575+
{:halt, assign(socket, key, sorted)}
1576+
end
1577+
1578+
def hooked_event(_event, _params, socket), do: {:cont, socket}
1579+
end
1580+
1581+
## Other examples
15121582
15131583
Attaching and detaching a hook:
15141584

0 commit comments

Comments
 (0)