Whether you’re part of a startup or a large-scale enterprise development team, you need to save time, money, and resources. Elixir and LiveView Native do that for you. Learn all the ways Elixir can help you launch a better digital product faster and more efficiently by reading our free Ebook, “The Business Value of Elixir”.
What does This Library Do and Why Should I Use It?
In Phoenix LiveView, we can send messages from the current component to the current view easily using send/2
, but sending messages from one component to another is not possible out of the box.
There has been a known hack, which is the official way of handling it, using send_update/3
and pattern-match in the c:update/2
callback for some special property names.
This hack relies on the fact that the
assigns
argument in the callback only contains the changed properties, and not all of them.
live_view_events
provides some macros to simplify sending messages to both views and other components and to handle those messages in the components.
Why Not Just Use the Workaround?
There are two main reasons to use the live_view_events
instead of the current workaround: consistency and efficiency.
First, consistency:
c:update/2
only deals with updating the assigns, and does not deal with messages.- All logic that deals with messages sent from the server lives inside a
handle_info
callback, be it in a live view or in a live component.
Secondly, because we’ve already written something not-generalized, simpler, hackier, and harder to maintain in the library as part of some of our projects, live_view_events
is more efficient than the existing workaround.
A Quick Introduction to the First Release
This first release includes everything you need to send and handle messages between live views and components or between components and components. This feature already brings value to the team by simplifying their workflow and the mental model of the messages, but it is also the first release.
The next planned feature will provide a way for a component to easily subscribe to PubSub events. We are really excited to land that feature soon-ish.
Sending Events
The library provides a couple of macros (notify_to/2
and notify_to/3
) to send messages to a variety of targets:
:self
is the same as sending it toself()
but, in some scenarios, it’s a useful way to tell your component to notify to its parent live view.- A PID
- A tuple of the form
{ComponentModule, "id"}
to notify a component. - A tuple of the form
{PID, ComponentModule, "id"}
to notify a component in a different live view.
Receiving Events
Events, like normal messages, are handled in a handle_info
callback. If you are in a live view, it is the normal handle_info
you are used to. If you are in a component, you need to do some simple wiring. (Don’t worry, we mean it when we say it’s simple). Just add a use LiveViewEvents
to your module and then override the update
callback with the following implementation:
defmodule MyAppWeb.MyComponent do
use MyAppWeb, :live_component
use LiveViewEvents
def update(assigns, socket) do
socket = handle_info_or_assign(socket, assigns)
{:ok, socket}
end
end
Then, you can add a handle_info
that deals with the messages you are ready to receive.
If you have an existing callback like the following:
def update(assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign(:something, value_from(assigns))
{:ok, socket}
end
The adaptation takes two steps:
- First, replace
assign(assigns)
withhandle_info_or_assign(assigns)
. - Refactor the second assign to take the value from
socket.assigns
and check if the dependant values changed withPhoenix.Component.changed?/2
. This refactor will prevent potentially expensive code from running unless needed and prevent hard-to-find bugs in the code. See the note above aboutassigns
only containing the properties whose values changed, and not all of them.
That Looks Cool, but is it Useful IRL?
Though it won’t be a full example, let’s explore one of the multiple cases where this can be handy. We might be building a complex form in which some fields require a non-standard input. That input would open a modal that would display several options. It can be anything, from an asset picker like WordPress does for images, or a contact selector, or anything custom you need for your app.
For this case, we’d need two components: one for handling the input and opening the modal with the selector, and the selector itself. These two components will send messages to each other.
defmodule MyAppWeb.InputWithModal do
use MyAppWeb, :live_component
def mount(socket) do
socket =
socket
|> assign(:modal_opened?, false)
|> assign(:selected_value, nil)
{:ok, socket}
end
def render(assigns) do
~H"""
<div id={@id}>
<button phx-click="open_modal" phx-target={@myself}>
Open modal
</button>
<%= hidden_input @form, @field, value: @selected_value %>
<.modal :if={@modal_opened?}>
<.live_component module={MyAppWeb.Picker} id={"#{@id}__modal"} />
</.modal>
</div>
"""
end
def handle_event("open_modal", _params, socket) do
socket = assign(socket, :modal_opened?, true)
{:noreply, socket}
end
end
The component above is barely functional: It can only open the modal and display the picker. Let’s take a look at the picker.
defmodule MyAppWeb.Picker do
use MyAppWeb, :live_component
use LiveViewEvents
def handle_event("select", params, socket) do
notify_to(socket.assigns.notify_to, socket.assigns.on_select, get_value_from_params(params))
end
end
The exact implementation of the picker does not matter, we just care that we are sending an event back to the component.
Let’s go back to the InputWithModal
component and add the following code:
- Add
use LiveViewEvents
. - Add custom
update/2
. Usinghandle_info_or_assign
would run thehandle_info/2
if needed. - Modify the
live_component/1
invocation adding the target and the event name, so the event can be sent. - Add new
handle_info/3
handling the event from the modal.
The final code looks like this:
defmodule MyAppWeb.InputWithModal do
use MyAppWeb, :live_component
use LiveViewEvents
def mount(socket) do
socket =
socket
|> assign(:modal_opened?, false)
|> assign(:selected_value, nil)
{:ok, socket}
end
def update(assigns, socket) do
socket = handle_info_or_assign(socket, assigns)
{:ok, socket}
end
def render(assigns) do
~H"""
<div id={@id}>
<button phx-click="open_modal" phx-target={@myself}>
Open modal
</button>
<%= hidden_input @form, @field, value: @selected_value %>
<%= if @modal_opened? do %>
<.modal>
<.live_component module={MyAppWeb.Picker} id={"#{@id}__modal"} notify_to={{__MODULE__, @id}} event_name="value_selected" />
</.modal>
<% end %>
</div>
"""
end
def handle_event("open_modal", _params, socket) do
socket = assign(socket, :modal_opened?, true)
{:noreply, socket}
end
def handle_info({"value_selected", value}, socket) do
socket = assign(socket, modal_opened?: false, selected_value: value)
{:noreply, socket}
end
end
Now, when the Picker
sends the event, the InputWithModal
correctly handles it!
Get Started
This blog post is a short introduction to the new library. You can find more in-depth information in the README
in live_view_events
and its documentation.
Hope you enjoy it and simplifies your work!