Fly.io runs apps close to users, by transmuting Docker containers into micro-VMs that run on our own hardware around the world. This is a recipe about making your stateless components in LiveView more reusable and generic. If you are interested in deploying your own Elixir project, the easiest way to learn more is to try it out; you can be up and running in just minutes.
On your LiveView page, you are using a custom component. You want to be able to pass HTML attributes into the component, but the component doesn’t know anything about the attributes being passed! You need a way to pass arbitrary attributes through and get them where you want them.
Problem
You’re getting into LiveView and starting to create more components to reuse in your application. Now you’re having a problem creating components and passing in common phx-click
or phx-value-myvalue
attributes and getting them where you want in your component’s markup.
Example:
You want to use the component like this:
<.special_button title="Yes, sign me up!"
phx-click="select-sign"
phx-value-elect="yes" />
The component’s markup generates something like this:
<div class="container-classes">
<div class="my-button-classes" I WANT THE CLICK SETTINGS HERE>
Yes, sign me up!
</div>
</div>
Notice the big “I WANT THE CLICK SETTINGS HERE”? That’s where I want the phx-click
and phx-value-elect
attributes to go. But the component is supposed to be reusable! Somewhere else it’s going to be a different phx-value-other
attribute.
How do I get the phx-click
, phx-value-*
and maybe even phx-target
attributes through to the component and control where they go?
Solution
In LiveView 0.16, the function assigns_to_attributes/2
was added. We can use this to help solve this problem!
Here’s why this function is helpful:
Useful for transforming caller assigns into dynamic attributes while stripping reserved keys from the result.
If we call assigns_to_attributes
with the assigns example from the problem above, this is returned:
assigns_to_attributes(assigns)
#=> ["title": "Yes, sign me up!",
#=> "phx-click": "select-sign",
#=> "phx-value-elect": "yes"]
The assigns_to_attributes
function takes a 2nd argument that comes in handy now. It let’s us exclude assigns from becoming attributes. Since we’re using :title
internally, we want to exclude it.
This is what it looks like when we exclude :title
.
assigns_to_attributes(assigns, [:title])
#=> ["phx-click": "select-sign",
#=> "phx-value-elect": "yes"]
Now that we’ve removed all the assigns that the component is using, we want to take the rest and just splat them where we want them but as HTML attributes.
How do we do that?
Let’s say our simple component looks like this:
def special_button(assigns) do
extra = assigns_to_attributes(assigns, [:title])
assigns =
assigns
|> assign(:extra, extra)
~H"""
<div class="container-classes">
<div class="my-button-classes" {@extra}>
</div>
</div>
"""
end
First, we get extra
to hold the attributes we want to render.
Next, we add :extra
to the assigns so our HEEx (~H
) template can use it.
Finally, the {@extra}
takes all the attributes not excluded and outputs them as HTML attributes on the div
we want! Our rendered markup ends up looking like this:
<div class="container-classes">
<div class="my-button-classes"
phx-click="select-sign"
phx-value-elect="yes">
Yes, sign me up!
</div>
</div>
Nice! That’s exactly what we were aiming for!
Discussion
The assigns_to_attributes/2
function is a good tool for making reusable LiveView components. It also gives us control over where common attributes like phx-click
and phx-value-*
get rendered.
This means our components can be more generic and reusable!