LocalLiveView implements functionality of Phoenix.LiveView
inside the browser in Popcorn runtime.
Instead of running the code on the server and sending updates via WebSocket, the local live view's code is sent to the browser on page load. Whenever you render a local live view on the page, it is run on the client.
LocalLiveView API similar to Phoenix.LiveView:
- it runs in a separate Elixir process,
- it has
mount/3,handle_params/3andrender/1callbacks, which behave the same way as in a regular live view, - it can spawn regular
Phoenix.Components andPhoenix.LiveComponents, - events from these components by default go to their parent local live view.
Thus, you can implement a simple local live view just like a regular live view:
defmodule DemoLive do
use LocalLiveView
def render(assigns) do
~H"""
Hello world!
"""
end
endand add it to your page using the local_live_view/1 component:
<.local_live_view view="DemoLive" />The main difference between LocalLiveView and Phoenix.LiveView is that
the former can also accept assigns and has an update callback, like
Phoenix.LiveComponent:
defmodule Cart do
use LocalLiveView
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, open: false)}
end
@impl true
def update(%{items: items}, socket) do
{:ok, assign(socket, :items, items)}
end
@impl true
def render(assigns) do
~H"""
<div>The cart has {length(@items)} items</div>
"""
end
endYou can reder it the following way:
<.local_live_view view="Cart" items={@items} />Sending events to the server
When a local live view is rendered inside a Phoenix.LiveView, it can
send events to that server-side live view by calling push_server_event/3
from Phoenix.LiveView.handle_event/3 or handle_info/2:
def handle_event("archive", %{"id" => id}, socket) do
{:noreply,
socket
|> assign(:items, archive_item(socket.assigns.items, id))
|> push_server_event("archive", %{"id" => id})}
endThis pattern gives optimistic updates: the event is handled locally
first (the optimistic edit), then delivered to the host LiveView's
handle_event/3, whose authoritative state can later override it.
Handling failed server pushes
A push_server_event/3 can fail: the page may have no host LiveView,
the websocket may be disconnected, or the host may reply with an error
or time out. When that happens, the view's handle_push_error/4
callback is invoked with the event, its params and server_assigns —
the last value of each assign received from the host server. The
default implementation feeds server_assigns through update/2,
rolling optimistic local edits back to the latest authoritative state.
Another way of communicating the server is by using mirror_sync/2.
Summary
Callbacks
Handles an event triggered from the template, such as phx-click.
Handles a message sent to the view's process.
Invoked with the query params of the page the view is rendered on.
Invoked when a push_server_event/3 fails: there is no host LiveView on the
page, the websocket is disconnected, or the host replies with an error or
times out.
Invoked once when the view is initialized, before the first render/1.
Returns the HEEx template for the view's current state.
Receives the assigns the host LiveView passes down.
Functions
Mimics Phoenix.LiveView.connected?/1. Always returns true.
Syncs the declared mirror assigns to the server-side mirror channel. Must be called from within a LocalLiveView callback (handle_event, handle_info) after assigns have been updated.
Navigates to the given path with a browser history push, then calls handle_params/3
with the new URL query params. No server round-trip.
Sends an event to the host (server) LiveView that mounts this local live view.
Types
@type unsigned_params() :: map()
Callbacks
@callback handle_event( event :: binary(), unsigned_params(), socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} | {:reply, map(), Phoenix.LiveView.Socket.t()}
Handles an event triggered from the template, such as phx-click.
Bindings work exactly as in Phoenix.LiveView, but the event is dispatched to
the local process instead of travelling to the server, so the following
render/1 happens without a round-trip.
def handle_event("increment", _params, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
endTo notify the host LiveView as well, push the event on with
push_server_event/3.
@callback handle_info(message :: term(), socket :: Phoenix.LiveView.Socket.t()) :: {:noreply, Phoenix.LiveView.Socket.t()}
Handles a message sent to the view's process.
A local live view runs as its own Elixir process inside the browser, so
anything that can reach that process arrives here — for example, a timer set
with Process.send_after/3:
def mount(_params, _session, socket) do
Process.send_after(self(), :tick, 1000)
{:ok, assign(socket, :time, Time.utc_now())}
end
def handle_info(:tick, socket) do
Process.send_after(self(), :tick, 1000)
{:noreply, assign(socket, :time, Time.utc_now())}
endThe resulting render is pushed to the DOM as a diff, exactly as after
handle_event/3. The default implementation ignores the message.
@callback handle_params( params :: unsigned_params(), uri :: String.t(), socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()}
Invoked with the query params of the page the view is rendered on.
Called after mount/3 and again after every push_patch/2. params holds
the query string decoded into a map with string keys and uri is the full
URL.
def handle_params(%{"tab" => tab}, _uri, socket) do
{:noreply, assign(socket, :tab, tab)}
end
@callback handle_push_error( event :: binary(), params :: unsigned_params(), server_assigns :: map(), socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()}
Invoked when a push_server_event/3 fails: there is no host LiveView on the
page, the websocket is disconnected, or the host replies with an error or
times out.
event and params are the event name and payload the failed push carried
(params has string keys, after a JSON round-trip, like handle_event/3
params). server_assigns holds the last value of each assign received from
the host server (through mount and update/2) — assigns only ever set
locally are absent.
The default implementation feeds server_assigns through update/2, as if
the host had re-sent them — restoring the latest authoritative state through
the view's usual derivation path. A view whose update/2 skips unchanged
data (e.g. guarded by a revision counter) should override this callback and
force its rollback explicitly.
@callback mount( params :: unsigned_params() | :not_mounted_at_router, session :: map(), socket :: Phoenix.LiveView.Socket.t() ) :: {:ok, Phoenix.LiveView.Socket.t()} | {:ok, Phoenix.LiveView.Socket.t(), keyword()}
Invoked once when the view is initialized, before the first render/1.
Use it to set up the initial assigns. Assigns coming from the host LiveView
are not delivered here — update/2 runs with them right after this
callback, before the first render.
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0, label: "Counter")}
end
@callback render(assigns :: Phoenix.LiveView.Socket.assigns()) :: Phoenix.LiveView.Rendered.t()
Returns the HEEx template for the view's current state.
Called on mount and again after every state change, exactly like
Phoenix.LiveView.render/1 — except the render happens in the browser, so
no diff travels over the network.
def render(assigns) do
~H"""
<p>{@label}: {@count}</p>
"""
end
@callback update(assigns :: map(), socket :: Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()}
Receives the assigns the host LiveView passes down.
The mechanism is the same as in Phoenix.LiveComponent and its
Phoenix.LiveComponent.update/2 callback.
Every attribute other than view given to local_live_view/1 is forwarded
here, the same way Phoenix.LiveComponent receives its assigns:
<.local_live_view view="Cart" items={@items} currency="EUR" />It runs right after mount/3 with the initial values, and then on every
re-render of the host LiveView. The host always sends the full set of
forwarded assigns, not a diff, so a view that needs to react only to actual
changes has to compare against its own assigns.
def update(%{items: items}, socket) do
{:ok, assign(socket, :items, items)}
endThe default implementation assigns everything it receives.
This callback is also called by the default implementation of handle_push_error/4.
Functions
Mimics Phoenix.LiveView.connected?/1. Always returns true.
Helps code reusability between server and local LiveViews.
Syncs the declared mirror assigns to the server-side mirror channel. Must be called from within a LocalLiveView callback (handle_event, handle_info) after assigns have been updated.
Navigates to the given path with a browser history push, then calls handle_params/3
with the new URL query params. No server round-trip.
Mirrors Phoenix.LiveView.push_patch/2 semantics.
Options
:to— the path to navigate to (required):replace— whentrue, replaces the current history entry instead of pushing a new one
Sends an event to the host (server) LiveView that mounts this local live view.
Callable from handle_event/3 or handle_info/2, it delivers
event/payload to the host LiveView's handle_event(event, payload, socket)
over the regular Phoenix websocket.
If the push fails (no host LiveView, disconnected socket, error reply or
timeout), the view's handle_push_error/4 callback is invoked.