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?

We chose this example precisely because there is an HTML 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!