Home Posts Tags Post Search Tag Search

Post 86

LiveView 01 - Gettings Started and Guessing Game

Published on: 2025-11-15 Tags: elixir, Side Project, LiveView, Html/CSS, Phoenix
Chapter 1: Get to Know LiveView
    Single page apps are there to make everything into one single page. That means that instead of having predefined pages for each part of the site you will need to build 1 page that services everything. 

    The LiveView Loop
        LiveView will receive events, link clicks keypresses etc
        Based on the events you write functions to transform the state.
        After the change LiveView will only change the portions that need to change.
        Now it will wait for actions from the user.

    Build a Simple LiveView

        We will first build a Single Page App that will tell a user that there guess is wrong. That is it. lol 

        Define the LiveView
            For a normal page there is a get and post setup that will be used to process requests but withing the LiveView you will need to use the idea of a get to the LiveView.live/3 function. Once that is invoked the Phoenix starts up a LiveView process. This will call the mount that will set the socket. We will use markup for the client. Once that is rendered a WebSocket will handle the connection. 
        
            Router -> Index.mount -> Index.render

            With this out of the way let's head to the router at pento/lib/pento_web/router.ex
                scope "/", PentoWeb do
                    pipe_through :browser

                    get "/", PageController, :home

                    # This line below
                    live "/guess", WrongLive
                end

            We are using the live/3 function there is a 3 param that is the live action but for now we can skip that. the /guess is the pattern that the url will invoke. the scope at the top means that you are going to invoke. PentoWeb.module in this case it will be PentoWeb.WrongLive. Let's create that module. Create the file at pento/pento_web/live/wrong_live.ex
                defmodule PentoWeb.WrongLive do
                    use PentoWeb, :live_view

                end

            This is using the live_view behavior. Now we need to check out what will happen when a user access the /guess route.

        Mount and Render the Live View
            Okay so now we are at the guess route the mount/3 function is responsible for setting the initial state of the page. Let's set some values in the initial state as such.
                def mount(_params, _session, socket) do
                    {:ok, assign(socket, score: 0, message: "Make a guess:")}
                end

            This mean our initial socket should have those values as a map. Once that is set and within the server the render/1 function is called and will need some sort of template to render. Let's add in that render function now like so.
                def render(assigns) do
                    ~H"""
                    <main class="px-4 py-20 sm:px-6 lg:px-8">
                    <h1 class="mb-4 text-4xl font-extrabold">Your score: {@score}</h1>
                    <h2>
                    {@message}
                    </h2>
                    <br />
                    <h2>
                    <%= for n <- 1..10 do %>
                        <.link
                        class="btn btn-secondary"
                        phx-click="guess"
                        phx-value-number={n}
                        >
                        {n}
                        </.link>
                    <% end %>
                    </h2>
                    </main>
                    """
                end

            This will use the ~H sigil to set all the html for the page. With this we can replace anything within <%= and %> with elixir code and {} is also elixir. This is just the beginning as it will also provide compile time validations. You can now head to the route on the server (mix phx.server) and see the page. Once it is up the JavaScript will establish a persistent WebSocket to await events. 

        Understanding the LiveView Loop
            As we have said before the HTTP request is started and then the mount and render happen, but for the first pass it is just a static SEO-friendly page. then the WebSocket is open and waiting. After that it will run those mount and render again to load all the dynamic portions of the code. Now the LiveView loop has been started. 

        Handle Events
            Looking at the below code you can see some important information. 
                <%= for n <- 1..10 do %>
                    <.link
                    class="btn btn-secondary"
                    phx-click="guess"
                    phx-value-number={n}
                    >
                    {n}
                    </.link>
                <% end %>

            First we can see a for loop as well as the a link. Each link wil have a value 1 .. 10 and will have a event triggered with the click. phx-click="guess" If you were to try and click on any of the buttons you will get an error message. This is because you haven't defined the guess event. We need to understand what all those lines mean, they are DOM element bindings. and there is 3 pieces of information that is being sent.
                The message name
                Map with the metadata related to the event
                current state of the socket

            Let's create the function for the guess event
                def handle_event("guess", %{"number" => guess}, socket) do
                    message = "Your guess: #{guess}. Wrong. Guess again"
                    score = socket.assigns.score - 1
                    {
                        :noreply,
                        assign(socket,
                        message: message,
                        score: score)
                    }
                end

            We take the pattern match to be sure that we are only dealing with the "guess" event and that the map will contain the "number" key. Then we change the message and then the score and return a tuple with the format {:noreply, socket}

    LiveView Transfers Data Efficiently
        Now let's get into what is being transferred and how. Remember that it will only change what is needed to be changed on the page. We can try and use a traffic inspect to see what is actually being transferred.
        
        Examine the Network Traffic
            This will involve opening up the developer tools and heading to the network tab and selecting the websocket that is associated with the local page. With the right view in the response you should be able to see something like
                ["4","23",
                    "lv:phx-GA8BYcBvYD4PIATD",
                    "phx_reply",
                    {"status":"ok",
                    "response":
                    {"diff":
                    {"3":
                    {"0":"-1","1":"Your guess: 2. Wrong. Guess again. "}
                    }
                    }
                    }
                ]
                1690487105.2571628

            This is showing that you are sending and receiving information and it is keeping track of the differences between the new information. Now let's add in a some other info to the page and see what changes with each click.

        Send Network Diffs
            We can now add in a clock and see what is changing. Add these line and this function.
                <%= @message %>
                It's <%= time() %>

                def time do
                    DateTime.utc_now() |> to_string()
                end
            
            Clicking doesn't update the time!!! That is because There is nothing that is telling the page to update that value. The socket didn't have a value to change with the updates so there was no change. We could change this with by adding in the time into the socket and then being sure to change the time with every guess event. I will leave that up to you but feel free to comment if you have any questions.

    Your Turn
        Remembering the way in which everything is done and the order:
            • Has internal state
            • Has a render function to render that state
            • Receives events which change state
            • Calls the render function when state changes
            • Only sends changed data to the browser
            • Needs no custom JavaScript

        Give It a Try
            • Assign a random number to the socket when the game is created, one the user will need to guess.
            • Check for that number in the handle_event for guess.
            • Show a winning message when the user guesses the right number and increment their score in the socket assigns.
            • Show a restart message and button when the user wins. Hint: you might want to check out the link/1 function and read this section of the docs on live navigation using the “patch” approach. You can treat this last challenge as a stretch goal. We’ll get into link/1 function in greater detail in upcoming chapters. (https://hexdocs.pm/phoenix_live_view/live-navigation.html)