View Source Electric.Phoenix.LiveView (Electric Phoenix v0.2.0)
Summary
Functions
Embed client configuration for a shape into your HTML.
Maintains a LiveView stream from the given Ecto query.
Handle Electric events within a LiveView.
Types
@opaque component_event()
@type event() :: replication_event() | state_event()
@opaque replication_event()
@opaque root_event()
@type stream_option() :: {:at, integer()} | {:limit, pos_integer()} | {:reset, boolean()} | {:client, struct()}
@type stream_options() :: [stream_option()]
Functions
Embed client configuration for a shape into your HTML.
<Electric.Phoenix.LiveView.electric_client_configuration
shape={MyApp.Todo}
key="todo_shape_config"
/>
This will put a <script>
tag into your page setting
window.todo_shape_config
to the configuration needed to subscribe to
changes to the MyApp.Todo
table.
<script>
window.todo_shape_config = {"url":"https://localhost:3000/v1/shape/todos" /* , ... */}
</script>
If you include a :script
slot then you have complete control over how the
configuration is applied.
<Electric.Phoenix.LiveView.electric_client_configuration shape={MyApp.Todo}>
<:script :let={configuration}>
const container = document.getElementById("root")
const root = createRoot(container)
root.render(
React.createElement(
MyApp, {
client_config: <%= configuration %>
},
null
)
);
</:script>
</Electric.Phoenix.LiveView.electric_client_configuration>
The configuration
variable in the :script
block is the JSON-encoded
client configuration.
<script>
const container = document.getElementById("root")
const root = createRoot(container)
root.render(
React.createElement(
MyApp, {
client_config: {"url":"https://localhost:3000/v1/shape/todos" /* , ... */}
},
null
)
);
</script>
Attributes
shape
(:any
) (required) - The Ecto query (or schema module) to subscribe to.key
(:string
) - The key in the top-levelwindow
object to put the configuration object. Defaults to"electric_client_config"
.client
(:any
) - Optional client. If not set defaults toElectric.Phoenix.client!()
.
Slots
script
- An optional inner block that allows you to override what you want to do with the configuration JSON.
@spec electric_stream( socket :: Phoenix.LiveView.Socket.t(), name :: atom() | String.t(), query :: Ecto.Queryable.t(), opts :: stream_options() ) :: Phoenix.LiveView.Socket.t()
Maintains a LiveView stream from the given Ecto query.
name
The name to use for the LiveView stream.query
AnEcto
query that represents the data to stream from the database.
For example:
def mount(_params, _session, socket) do
socket =
Electric.Phoenix.LiveView.electric_stream(
socket,
:admins,
from(u in Users, where: u.admin == true)
)
{:ok, socket}
end
This will subscribe to the configured Electric server and keep the list of
:admins
in sync with the database via a Phoenix.LiveView
stream.
Updates will be delivered to the view via messages to the LiveView process.
To handle these you need to add a handle_info/2
implementation that receives these:
def handle_info({:electric, event}, socket) do
{:noreply, Electric.Phoenix.LiveView.electric_stream_update(socket, event)}
end
See the docs for
Phoenix.LiveView.stream/4
for details on using LiveView streams.
Lifecycle Events
Most {:electric, event}
messages are opaque and should be passed directly
to the electric_stream_update/3
function, but there are two events that are
outside Electric's replication protocol and designed to be useful in the
LiveView component.
{:electric, {stream_name, :loaded}}
- sent when the Electric event stream has passed from initial state to update mode.This event is useful to show the stream component after the initial sync. Because of the streaming nature of Electric Shapes, the intitial sync can cause flickering as items are added, removed and updated.
E.g.:
# in the LiveView component def handle_info({:electric, {_name, :live}}, socket) do {:noreply, assign(socket, :show_stream, true)} end # in the template <div phx-update="stream" class={unless(@show_stream, do: "opacity-0")}> <div :for={{id, item} <- @streams.items} id={id}> <%= item.value %> </div> </div>
{:electric, {stream_name, :live}}
- sent when the Electric stream is inlive
mode, that is the initial state has loaded and the client is up-to-date with the database and is long-polling for new events from the Electric server.
If your app doesn't need this extra information, then you can ignore them and just have a catch-all callback:
def handle_info({:electric, event}, socket) do
{:noreply, Electric.Phoenix.LiveView.electric_stream_update(socket, event)}
end
Electric.Phoenix.LiveView.electric_stream_update
will just ignore the
lifecycle events.
Sub-components
If you register your Electric stream in a sub-component you will still receive Electric messages in the LiveView's root/parent process.
Electric.Phoenix
handles this for you by encapsulating component messages
so it can correctly forward on the event to the component.
So in the parent LiveView
process you handle the :electric
messages as
above:
defmodule MyLiveView do
use Phoenix.LiveView
def render(assigns) do
~H"""
<div>
<.live_component id="my_component" module={MyComponent} />
</div>
"""
end
# We setup the Electric electric_stream in the component but update messages will
# be sent to the parent process.
def handle_info({:electric, event}, socket) do
{:noreply, Electric.Phoenix.LiveView.electric_stream_update(socket, event)}
end
end
In the component you must handle these events in the
Phoenix.LiveComponent.update/2
callback:
defmodule MyComponent do
use Phoenix.LiveComponent
def render(assigns) do
~H"""
<div id="users" phx-update="stream">
<div :for={{id, user} <- @streams.users} id={id}>
<%= user.name %>
</div>
</div>
"""
end
# Equivalent to the `handle_info({:electric, {stream_name, :live}}, socket)` callback
# in the parent LiveView.
def update(%{electric: {_stream_name, :live}}, socket) do
{:ok, socket}
end
# Equivalent to the `handle_info({:electric, event}, socket)` callback
# in the parent LiveView.
def update(%{electric: event}, socket) do
{:ok, Electric.Phoenix.LiveView.electric_stream_update(socket, event)}
end
def update(assigns, socket) do
{:ok, Electric.Phoenix.electric_stream(socket, :users, User)}
end
end
@spec electric_stream_update(Phoenix.LiveView.Socket.t(), event(), Keyword.t()) :: Phoenix.LiveView.Socket.t()
Handle Electric events within a LiveView.
def handle_info({:electric, event}, socket) do
{:noreply, Electric.Phoenix.LiveView.electric_stream_update(socket, event, at: 0)}
end
The opts
are passed to the Phoenix.LiveView.stream_insert/4
call.