We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
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.