Home Posts Post Search Tag Search

LiveView 18 - Chapter 7: Live Components
Published on: 2026-01-03 Tags: elixir, Blog, Side Project, State, LiveView, Html/CSS, Phoenix

Chapter 7 Live Components

One thing to keep in mind while using and building components is that they have their own state. This can be helpful as it will compartmentalize some things but it makes understanding the state a lot harder for debugging and working with many people on a team.

Build the Live Demographic Form Component
    Here is the plan:
        Implement a live component to house our demographic
        Render the form markup using the simple_form/1 function
        Teach it to respond to user input and save the demographic for later.

    Define the Live Component
        Let's start by creating a new file pento/lib/pento_web/live/demographic_live/form.ex
                defmodule PentoWeb.DemographicLive.Form do
                    use PentoWeb, :live_component
                    alias Pento.Survey
                    alias Pento.Survey.Demographic
                end
        We will use the simple_form/1 to help render the form but we will be leveraging this style for the flow (CRC)
            inputs |> construct() |> convert()

        Construct will establish the initial state of the form. The convert will take that state and transform it into html. We will be using the update callback for reasons that we will get into later. Let's go over the basic callbacks we will be using here:
            mount/1
                Called once to set the initial state of the socket.
            update/2
                Will take the assigns given to the live_component/3 and the socket. This will be used to add more detail every time the live_component/3 is called.
            render/1
                socket.assigns works to render the page

        As we will only call the mount once and then it will be update and render we will establish the initial state of the component. We already have the survey_live.ex or the survey_live.html.heex with the demographic info or "coming soon" so let's implement the form.ex update/2
                @impl true
                def update(assigns, socket) do
                    socket =
                    socket
                    |> assigns(assigns)
                    |> assigns_demographic()
                    |> clear_form()

                    {:ok, socket}
                end

            # Now we can build the helper function that will assign a demographic and an empty form.
                defp assigns_demographic(%{assigns: %{current_scope: current_scope}} = socket) do
                    assign(
                    socket,
                    :demographic,
                    %Demographic{
                        user_id: current_scope.id
                    }
                    )
                end

                defp assign_form(socket, changeset) do
                    assign(socket, :form, to_form(changeset))
                end

                defp clear_form(%{assigns: %{demographic: demographic}} = socket) do
                    current_scope = socket.assigns.current_scope

                    changeset =
                    Survey.change_demographic(current_scope, demographic)

                    assign_form(socket, changeset)
                end
        These will build the demographic into the assigns, assign a for into the socket, and build a changeset around and empty for and assign it to the socket.

    Render the Demographic Form
        Now we get to render the form so that we can have the user see all the needed information. The book wants us to head to the html.heex for the form but again we will use the render.
                @impl true
                def render(assigns) do
                    ~H"""
                    <div>
                    <.form
                        for={@form}
                        phx-submit="save"
                        id={@id}
                        phx-target={@myself}
                    >
                        <.input
                        field={@form[:gender]}
                        type="select"
                        label="Gender"
                        options={["female", "male", "other", "prefer not to say"]}
                        />
                        <.input
                        field={@form[:year_of_birth]}
                        type="select"
                        label="Year of Birth"
                        options={Enum.reverse(1920..2025)}
                        />
                        <div>
                        <.button phx-disable-with="Saving...">Save</.button>
                        </div>
                    </.form>
                    </div>
                    """
                end
        You need to wrap this in a div in order to make it work without the html.heex. You have now used the new form to render on the survey_live.ex page. Now if you try and submit there will be an error that will be because of the handle_event not being there.

        This will render our form in the way we want. In this case we are using the .form component that is built in and we can use it as a component. New we can head to the survey_live.ex in order to leverage the new component.
                alias PentoWeb.DemographicLive.{Show, Form}

                @impl true
                def render(assigns) do
                    ~H"""
                    <Layouts.app flash={@flash} current_scope={@current_scope}>
                    <Component.hero content="Survey">
                        Please fill out the survey.
                    </Component.hero>
                    <div class="container mx-auto px-4 py-8 max-w-4xl">
                        <%= if @demographic do %>
                        <Show.details demographic={@demographic} />
                        <% else %>
                        <.live_component
                            module={Form}
                            id="demographic_form"
                            current_scope={@current_scope}
                        />
                        <% end %>
                    </div>
                    </Layouts.app>
                    """
                end
        Now if you never put the phx-target={@myself} the error that we would get at this point would be that the handle_event was not created for the Survey_Live.ex not the form. Adding in the {@myself} makes sure that the liveview is targeting the right page.