Skip to content

A simple library to unify sending and receiving messages in LiveView components

License

Notifications You must be signed in to change notification settings

DockYard/live_view_events

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiveViewEvents

Elixir CI

LiveViewEvents provides a set of tools to send events between components.

Installation

Add live_view_events to your list of dependencies in mix.exs like:

def deps do
  [
    {:live_view_events, "~> 0.1.0"}
  ]
end

Usage

Add use LiveViewEvents whenever you want to use any of the features of the libraries.

Sending events to LiveView components

You can send events to LiveView components by using notify_to/2 or notify_to/3 (the only difference being that the latter sends some extra params). These functions accept a target as first argument and a message name as second. Targets can be any of:

  • nil makes the function call be a noop. Useful if you want to only send a message if an explicit target has been passed in.
  • :self to send to self().
  • A PID.
  • A tuple of the form {Module, "id"} to send a message to a LiveView.Component in the same process.
  • A tuple of the form {pid, Module, "id"} to send a message to a LiveView.Component in a different process.

You an handle these messages on the handle_info/2 callback on your LiveView components. For being able to do so, you need to use the handle_info_or_assign/2 macro on the update/3 callback of your component.

Example

In our view, we are going to have two components. The first one is a Sender that can send an event to whatever is being passed. Let's dig into the implementation:

defmodule MyAppWeb.Components.Sender do
  use MyAppWeb, :live_component
  use LiveViewEvents

  def mount(socket) do
    socket = assign(socket, :notify_to, :self)

    {:ok, socket}
  end

  def render(assigns) do
    ~H[<button type="button" phx-click="clicked" phx-target={@myself}>Send event</button>]
  end

  def handle_event("clicked", _params, socket) do
    notify_to(socket.assigns.notify_to, :sender_event, :rand.uniform(100))
    {:noreply, socket}
  end
end

This component will send a :sender_event message to whatever we pass to the notify_to attribute. It sends a random number between 0 and up to 100 as parameter.

Let's dig into the receiver:

defmodule MyAppWeb.Components.Receiver do
  use MyAppWeb, :live_component
  use LiveViewEvents

  def mount(socket) do
    socket = assign(socket, :messages, [])

    {:ok, socket}
  end

  def update(assigns, socket) do
    socket = handle_info_or_assign(socket, assigns)

    {:ok, socket}
  end

  def render(assigns),
    do: ~H[<ul><li :for={m <- @messages}><%= m %></li></ul>]

  def handle_info({:sender_event, num}, socket) do
    {:noreply, update(socket, :messages, &[num | &1])}
  end
end

This component will receive messages and handle them in handle_info/2 because it is using handle_info_or_assign/2 in their LiveComponent.update/2. It will add the received messages to socket.assigns.messages and display them. As the reader can see, it is pattern matching against :sender_event messages. When notify_to/3 is used, the message sent is a tuple containing the event name as first element, and the params as second the second element.

Finally, let's take a look at what the live view template would need to look like for this to work:

<div class="contents">
  <.live_component
    module={MyAppWeb.Components.Sender}
    id="sender"
    notify_to={{MyAppWeb.Components.Receiver, "receiver"}}
  />
  <.live_component module={MyAppWeb.Components.Receiver} id="receiver" />
</div>

In this template, we set notify_to to the tuple {MyAppWeb.Components.Receiver, "receiver"}. The first element of the tuple is the live component module and the second is the id. Optionally, the tuple can contain an extra first element that needs to be a PID. Though this might not be useful in the application code (there are way better ways to send events between processes), it is quite useful when testing. When testing a LiveView it creates a new process for it. Its PID can be accessed through view.pid.

Why are messages without params still receiving empty params?

To normalize how to handle messages. If we could have handle_info("message_name", _socket) or handle_info({"message_name", _params}, socket), you would need to think about whether the message contains params or not and then use a tuple. With this normalization, you only care about the message name and whether you need the params or not. Also, after using similar approaches in the past, sometimes you want to send params and sometimes you don't need to. In cases like these, it is helpful not having a different behaviour notifying an event without params and sending an event with empty params.

About

A simple library to unify sending and receiving messages in LiveView components

Resources

License

Stars

Watchers

Forks

Packages

No packages published