Components
Functional components
Read time: 5 minutes
Reusing code is the key to building a maintainable system. In LiveView there is more than one way for you to reuse HEEx code. In the following lessons we will explore functional components for your views and, bit by bit, understand how they work and their possibilities.
#Understanding the problem
Create and run a duplicated_code.exs
file:
Mix.install([
{:liveview_playground, "~> 0.1.5"}
])
defmodule PageLive do
use LiveviewPlaygroundWeb, :live_view
def render(assigns) do
~H"""
<button
type="button"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
>
Default
</button>
<button
type="button"
class="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800"
>
Green
</button>
<button
type="button"
class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
>
Red
</button>
<button
type="button"
class="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
>
Yellow
</button>
"""
end
end
LiveviewPlayground.start(scripts: ["https://cdn.tailwindcss.com"])
In this example we introduced the scripts
property in our LiveviewPlayground.start/1
which accepts a list of JavaScript scripts and adds them to our HTML. We will use Tailwind instead of writing CSS directly because nowadays Phoenix already comes with this library installed by default.
Imagine you are developing a large project and the styles above are used for buttons. Every time you need a new button you would have to copy and paste a ton of classes. Even if there were one or two classes whenever we needed to change them you would have to change every corner of your application.
#Creating a functional component
In previous lesson we saw the <.link>
component being used to render our links. To create a new component, simply create a function with any name that receives a single variable called assigns. Create and run first_component.exs
:
Mix.install([
{:liveview_playground, "~> 0.1.5"}
])
defmodule PageLive do
use LiveviewPlaygroundWeb, :live_view
def render(assigns) do
~H"""
<.button>Default</.button>
<.button>Green</.button>
<.button>Red</.button>
<.button>Yellow</.button>
"""
end
def button(assigns) do
~H"""
<button
type="button"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
>
Default
</button>
"""
end
end
LiveviewPlayground.start(scripts: ["https://cdn.tailwindcss.com"])
Just like render/1
, we have another function that returns HEEx and takes an argument called assigns. To use a component defined in the current file, simply write <.component_name></.component_name>
in your HEEx.
Why do components have a .
at the beginning?
button
tag. The .
at the beginning of the component serves to make it obvious that this tag refers to a functional component and not an HTML tag.
Unlike our first code you may notice that all buttons now show the same text: "Default" despite the fact that <.button>
has a different text! This happens because at the moment we are the creators of the new component, we must teach HEEx how the content of the inner block should be rendered. Create and run component_inner_block.exs
:
Mix.install([
{:liveview_playground, "~> 0.1.5"}
])
defmodule PageLive do
use LiveviewPlaygroundWeb, :live_view
def render(assigns) do
~H"""
<.button>Default</.button>
<.button>Green</.button>
<.button>Red</.button>
<.button>Yellow</.button>
"""
end
def button(assigns) do
~H"""
<button
type="button"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
>
<%= render_slot(@inner_block) %>
</button>
"""
end
end
LiveviewPlayground.start(scripts: ["https://cdn.tailwindcss.com"])
The only change that happened in our <.button>
was that we added the render_slot/2
function by passing an assign called @inner_block
. This assign is automatically defined in components and called a slot
type and contains the HTML inside your <.component>
. From now on, anything written inside <.button>
will be rendered there.
#Customizing components with attributes
Originally each button had its own color whereas now they all have the same style. We can customize our buttons using assigns passes as attribute. Create and run custom_button_colors.exs
:
Mix.install([
{:liveview_playground, "~> 0.1.5"}
])
defmodule PageLive do
use LiveviewPlaygroundWeb, :live_view
def render(assigns) do
~H"""
<.button color="blue">Default</.button>
<.button color="green">Green</.button>
<.button color="red">Red</.button>
<.button color="yellow">Yellow</.button>
"""
end
def button(assigns) do
~H"""
<button
type="button"
class={"text-white bg-#{@color}-700 hover:bg-#{@color}-800 focus:ring-4 focus:ring-#{@color}-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-#{@color}-600 dark:hover:bg-#{@color}-700 focus:outline-none dark:focus:ring-#{@color}-800"}
>
<%= render_slot(@inner_block) %>
</button>
"""
end
end
LiveviewPlayground.start(scripts: ["https://cdn.tailwindcss.com"])
Now each use of the button has an assign of color="..."
and we can customize our buttons in a much simpler way without duplicating code.
#Recap!
-
You can create components in your LiveViews if you create a function that receives
assigns
and returns HEEx. -
HTML components and tags are separated by the presence of a leading
.
at on its name to avoid conflicts. -
In a component you decide where to render the child slot using
render_slot(@inner_block)
. - Your components can reuse code efficiently with attributes.
Feedback
Got any feedback about this page? Let us know!