When designing a new component, one decision that has to be made is where to keep its state. Phoenix LiveView provides different ways to handle state depending on the type of the component you're using.
Let's take a closer look at each one of them.
State management in functional components is quite simple. After all, there's no state to be
managed. It works as just like a pure function. You define properties that will be merged into
assigns will be passed to the
render/1 function and that's it. You cannot
define events that can change any of the assigned values. If you want to do that, you'll have to
change the values passed as properties in the parent component, forcing the
to be called again with the updated values.
Handling state with
Consider the following Dialog component:
Dialog above is a stateless component, i.e. it doesn't own its state and all
state handling must be done in the parent
Defining a new
:show_dialogto hold the state
Define the related
handle_event/3callbacks to show/hide the dialog
Here's our dialog in action along with the parent LiveView's code:
Notice that even the
"hide_dialog" event which is dispatched by the dialog's
internal "Ok" button had to be defined in the live view.
One problem that might arise with this approach is that, as the parent live view gets larger holding more children with more complex states and events, a lot of code needs to be written in the live view to manage the state of each individual component.
Handling state with
In the last section, we saw that having lots of event handlers in a single LiveView might not be desired. One way to tackle this problem is by using a stateful LiveComponent instead. The great thing about live components is that they can handle their own state, consequently, we can move all component's related event handlers to the component itself.
That sounds really great but it raises a question. If the parent doesn't own the dialog's state anymore, how can the dialog be opened by the parent?
The LiveView documentation states that "send_update/2 is useful for updating a component that entirely manages its own state, as well as messaging between components."
That's exactly what we need! We can use
send_update/2 to tell the dialog to update
itself, setting the
:show assign to
send_update/2 from the parent view is a valid solution,
from the design perspective, explicitly setting
:show might not be ideal.
Remember that the fact we need to change the
:show assign in order to
show/hide the dialog is an implementation detail. Leaking internal details
of the state is problematic. Any change in the shape of the state might break
our code in many different places. Maybe for a simple case like our show/hide
that wouldn't be a big issue, but for more complex actions that update multiple
assigns, maintaining those actions in sync may become a nightmare. The solution,
however, is quite simple, we can define a public function
show/1 in the
module to encapsulate the changes in the state.
Here's the updated version of our
As you can see, the dialog's state is now opaque to the parent live view and any change to the internal state should only be performed through the component's public API.
Let's take a look at our new dialog in action along with the parent's live view code: