Follow Along: LiveView Counter
Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.9", override: true},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
Navigation
Home Report An Issue LiveViewMath GameReview Questions
Upon completing this lesson, a student should be able to answer the following questions.
- How do we mount a LiveView on a given route in the router?
- What is the lifecycle of a LiveView?
- How is information stored and set in the socket?
- How do we send messages to a LiveView and handle them?
Follow Along: Counter
We’re going to build a LiveViewCounter
application to learn more about LiveView. Users will click a button that increments the count on the page.
You’re also going to create a form with text input that can increment the count by a specific amount.
Scaffold Application
First, let’s create a new LiveViewCounter
Phoenix application.
$ mix phx.new live_view_counter --no-ecto
Start The Server
We can start the server normally.
$ mix phx.server
The server should automatically reload every time we change our code. If you are a Linux user, you may have to install inotify-tools.
Define Live Route
The Phoenix.Router uses the Phoenix.LiveView.Router.live/4 macro to define the route handled by the Phoenix.LiveView.
Let’s replace the default index route with a LiveView. We haven’t yet created the CounterLive
LiveView, but we’ll do that next.
# Counter_web/router.ex
scope "/", LiveViewCounterWeb do
pipe_through :browser
live "/", CounterLive, :counter
end
Create A LiveView
CounterLive
Now, create a new live_view_counter_web/live
folder. This folder will store our LiveViews. In the folder, create a live_view_counter_web/live/counter_live.ex
with the following content.
The ~H
is a sigil used to define a HEEX (HTML + EEx) template. Remember that sigils are a textual way of working with data in Elixir.
defmodule LiveViewCounterWeb.CounterLive do
use LiveViewCounterWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
Counter
"""
end
end
Display The Count
Store the initial count in the socket and display it on the page using the embedded Elixir syntax.
defmodule LiveViewCounterWeb.CounterLive do
use LiveViewCounterWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, :count, 0)}
end
def render(assigns) do
~H"""
Counter
Count: <%= @count %>
"""
end
end
Increment The Count
To increment this count, we’ll make a button the user can click. This button will trigger a phx-click
event with the "increment"
message. This event is then handled by a corresponding handler, which increments the count in state.
defmodule LiveViewCounterWeb.CounterLive do
use LiveViewCounterWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, :count, 0)}
end
def render(assigns) do
~H"""
Count: <%= @count %>
<.button id="increment-button" phx-click="increment">Increment
"""
end
def handle_event("increment", _, socket) do
{:noreply, assign(socket, count: socket.assigns.count + 1)}
end
end
Increment Count Form
Here’s a simple form that submits the value provided and increments the count by that value.
defmodule LiveViewCounterWeb.CounterLive do
use LiveViewCounterWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0, form: to_form(%{"increment_by" => 1}))}
end
def render(assigns) do
~H"""
Counter
Count: <%= @count %>
<.button id="increment-button" phx-click="increment">Increment
<.simple_form id="increment-form" for={@form} phx-submit="increment_by">
<.input type="number" field={@form[:increment_by]} label="Increment Count"/>
<:actions>
<.button>Increment
"""
end
def handle_event("increment", _, socket) do
{:noreply, assign(socket, count: socket.assigns.count + 1)}
end
def handle_event("increment_by", params, socket) do
{:noreply,
assign(socket,
count: String.to_integer(params["increment_by"]) + socket.assigns.count
)}
end
end
Increment Count Form Validation
This form demonstrates using both a phx-change
and phx-submit
event to validate the count and display errors. if the data is valid, then the count is incremented by the amount in the forms :increment_by
field.
defmodule LiveViewCounterWeb.CounterLive do
use LiveViewCounterWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0, form: to_form(%{"increment_by" => 1}))}
end
def render(assigns) do
~H"""
Counter
Count: <%= @count %>
<.button id="increment-button" phx-click="increment">Increment
<.simple_form id="increment-form" for={@form} phx-change="change" phx-submit="increment_by">
<.input type="number" field={@form[:increment_by]} label="Increment Count"/>
<:actions>
<.button>Increment
"""
end
def handle_event("increment", _, socket) do
{:noreply, assign(socket, count: socket.assigns.count + 1)}
end
def handle_event("change", params, socket) do
socket =
case Integer.parse(params["increment_by"]) do
:error ->
assign(socket,
form: to_form(params, errors: [increment_by: {"Must be a valid integer", []}])
)
_ ->
assign(socket, form: to_form(params))
end
{:noreply, socket}
end
def handle_event("increment_by", params, socket) do
socket =
case Integer.parse(params["increment_by"]) do
:error ->
assign(socket,
form: to_form(params, errors: [increment_by: {"Must be a valid integer", []}])
)
{int, _rest} ->
assign(socket, count: socket.assigns.count + int)
end
{:noreply, socket}
end
end
Tests
Here are some simple test examples for testing the increment button and the increment form in our LiveView. They aren’t comprehensive, but they provide a good skeleton for you to understand the basics of LiveView testing.
# Test/live_view_counter_web/live/counter_live_test.exs
defmodule LiveViewCounterWeb.CounterLiveTest do
use LiveViewCounterWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "increment count", %{conn: conn} do
{:ok, view, html} = live(conn, "/")
assert html =~ "Count: 0"
assert view
|> element("#increment-button", "Increment")
|> render_click() =~ "Count: 1"
end
test "increment by count", %{conn: conn} do
{:ok, view, html} = live(conn, "/")
assert html =~ "Count: 0"
assert view
|> form("#increment-form")
|> render_submit(%{increment_by: "3"}) =~ "Count: 3"
end
end
Finishing Touches
Make sure all tests pass. Remove any unnecessary boilerplate generated by Phoenix such as the page controller and page controller tests.
Bonus: Your Turn
Make the count automatically increment every second.
Further Reading
Consider the following resource(s) to deepen your understanding of the topic.
- HexDocs: LiveView
- HexDocs: Phoenix.HTML
- Elixir Schools: LiveView
- PragProg: Programming Phoenix LiveView
Commit Your Progress
DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.
Run git status
to ensure there are no undesirable changes.
Then run the following in your command line from the curriculum
folder to commit your progress.
$ git add .
$ git commit -m "finish Follow Along: LiveView Counter exercise"
$ git push
We’re proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.
We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.