View Source LiveEvent behaviour (live_event v0.1.1)

Standardized event handling in LiveViews and LiveComponents.

the-problem

The problem

LiveView currently has two built-in mechanisms for sending messages between views and components:

  • send/2 -> handle_info/2 for sending to a LiveView
  • send_update/3 -> update/2 for sending to a LiveComponent

Picking a singular approach limits component reusability, and using both approaches results in an inconsistent event API.

the-solution

The solution

LiveEvent standardizes these systems into a singular flow:

When use LiveEvent.LiveComponent or use LiveEvent.LiveView is invoked, it hooks into the lifecycle of the view or component to transparently add support for the LiveEvent.handle_event/4 callback.

event-destinations

Event destinations

Imagine a LiveComponent that has an :on_selected event assign that is raised like so:

emit(socket, :on_selected)

To handle the event on a LiveView, pass a pid to the event assign.

<.live_component module={MyComponent} id="foo" on_selected={self()}>

To handle the event on a LiveComponent, pass {module, id} to the event assign.

<.live_component module={MyComponent} id="foo" on_selected={{__MODULE__, @id}}>

In both cases, the event is handled by the LiveEvent.handle_event/4 callback.

# On a LiveView OR LiveComponent
def handle_event(:on_selected, {MyComponent, "foo"}, _payload, socket), do: ...

Example

defmodule MyLiveView do
  use Phoenix.LiveView
  use LiveEvent.LiveView

  def render(assigns) do
    ~H"""
    <.live_component module={MyLiveComponent} id="my-component" on_selected={self()} />
    """
  end

  def handle_event(:on_selected, {MyLiveComponent, "my-component"}, %{at: at}, socket) do
    IO.puts("Selected at #{at}")
    {:ok, socket}
  end
end

defmodule MyLiveComponent do
  use Phoenix.LiveComponent
  use LiveEvent.LiveComponent

  def render(assigns) do
    ~H"""
    <button phx-click="click" phx-targt={@myself}>Click me</button>
    """
  end

  def handle_event("click", _, socket) do
    {:noreply, emit(socket, :on_selected, %{at: DateTime.utc_now()})}
  end
end

Link to this section Summary

Functions

Raise an event from a LiveView or LiveComponent.

Send an event to a LiveView or LiveComponent.

Link to this section Callbacks

Link to this callback

handle_event(name, source, payload, socket)

View Source (optional)
@callback handle_event(
  name :: atom(),
  source :: any(),
  payload :: any(),
  socket :: LiveView.Socket.t()
) :: {:ok, socket :: LiveView.Socket.t()}

Handle an event message sent by emit/3 or send_event/4.

Events sent via emit/3 have a source argument of the form {module, id}.

compared-to-handle_event-3

Compared to handle_event/3

This callback is distinct from LiveView's handle_event/3 callback in a few important ways:

  • The arity is different
  • The result is {:ok, socket}, not {:noreply, socket}
  • LiveEvent uses atoms for event names, not strings
  • LiveEvent always originate from the server, not the client

example

Example

def handle_event(:on_profile_selected, {MyLiveComponent, _id}, profile_id, socket), do: ...

Link to this section Functions

Link to this function

emit(socket, event_name, payload \\ nil)

View Source
@spec emit(
  socket :: Phoenix.LiveView.Socket.t(),
  event_name :: atom(),
  payload :: any()
) ::
  Phoenix.LiveView.Socket.t()

Raise an event from a LiveView or LiveComponent.

The event_name argument is the name of the optional socket assign whose value specifies the destination for the event. The name of the emitted event defaults to the name of the assign.

Possible assign values are:

  • nil to not raise the event
  • a pid, to send the event to a LiveView
  • {pid, event_name} to send the event to a LiveView with a custom event name
  • {module, id} to send the event to a LiveComponent
  • {module, id, event_name} to send the event to a LiveComponent with a custom event name

Returns the unmodified socket.

Link to this function

send_event(destination, event_name, payload, opts \\ [])

View Source
@spec send_event(
  destination :: LiveView.Event.destination(),
  event_name :: atom(),
  payload :: any(),
  opts :: keyword()
) :: :ok

Send an event to a LiveView or LiveComponent.

Typically, emit/3 should be used to send events, but this function can be used if more control is needed.

To send to a LiveView (or any other process), specify a pid (usually self()) as the destination. To send to a LiveComponent, specify {module, id} as the destination. The event can handled by the LiveEvent.handle_event/4 callback.

When sending to an arbitrary process, the message will be a LiveEvent.Event struct, although you should not normally have to deal with that directly.

options

Options

  • :source - where the event originated from; defaults to nil

examples

Examples

send_event(self(), :on_selected, %{profile_id: 123})
# => def handle_event(:on_selected, _source, %{profile_id: id}, socket), do: ...

send_event({MyComponent, "my-id"}, :on_selected, %{profile_id: 123})
# => def handle_event(:on_selected, _source, %{profile_id: id}, socket), do: ...