Programming Phoenix LiveView Book | How to do the last task of give it a try in the 1st chapter?

I’ve started reading the book Programming Phoenix LiveView recently and at the end of the chapter we are supposed to do a few tasks which are:

Now that you’ve seen a basic LiveView “game”, you can tweak the game so that the user can actually win. You’ll need to:

• 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.

• Award points for a right guess.

• Show a winning message when the user wins.

• Show a restart message and button when the user wins. Hint: you might
want to check out the live_patch/2 function to help you build that button. You can treat this last challenge as a stretch goal. We’ll get into live_patch/2 in greater detail in upcoming chapters.

I’ve completed 4 out of the 5 tasks above except the last one.

How can I add a button to my page inside the handle_event/3 function (not sure whether that’s the correct place to do that anyway) which is triggered when the user guesses the right number? I’ve looked at live_patch function but I think first I need to figure out a way to add that live_patch to my DOM, but I couldn’t figure out how. You can see the related code below.

# wrong_live.ex

defmodule PentoWeb.WrongLive do
  use PentoWeb, :live_view

  def mount(_params, _session, socket) do
    {
      :ok,
      assign(
        socket,
        score: 0,
        message: "Guess a number.",
        num: :rand.uniform(10) |> to_string()
      )
    }
  end

  def handle_event("guess", %{"number" => guess} = data, %{assigns: %{num: guess}} = socket) do
    correct_answer_msg = "You won! The number was #{guess}"
    score = socket.assigns.score + 1

    {
      :noreply,
      assign(
        socket,
        message: correct_answer_msg,
        score: score
      )
    }
  end

  def handle_event("guess", %{"number" => guess} = data, %{assigns: %{num: num}} = socket) do
    wrong_answer_msg = "Your guess: #{guess}. Wrong. Guess again."
    score = socket.assigns.score - 1

    {
      :noreply,
      assign(
        socket,
        message: wrong_answer_msg,
        score: score
      )
    }
  end

  def render(assigns) do
    ~L"""
    <h1>Your score: <%= @score %></h1>
    <h2>
      <%= @message %>
    </h2>
    <h2>
      <%= for n <- 1..10 do %>
        <a href="#" phx-click="guess" phx-value-number="<%= n %>"><%= n %></a>
      <% end %>
    </h2>
    """
  end
end

You’ll want to add your button/link on your render function.
If that’s all your missing, this will do the deal: <%= live_patch "Play again !", to: Routes.live_path(@socket, PentoWeb.WrongLive) %>.
I did this a while ago so if you’re still missing something, let us know.

This can be helpful to understand the differences between all the redirection possibilities.

1 Like

Oh, I was always thinking about a way to later add the button to the DOM when the user guesses it right. I think instructions were a bit vague there.

For that case, you could keep a boolean assign like answered_correctly which gets set to true after a correct guess. Then in the render you can add a button/live_patch inside a conditional like

<%= if @answered_correctly do %>
  # button
<% end %>
1 Like

Since it’s on the first chapters, I would imagine they would prefer something more simple.
If you still want to avoid having the button being there all the time and only pop when the user wins, the suggestion above is a simple way to do that.
Set the assign to false on mount, and set it to true on your handle event for winning guess.
You might need a update function to set back to false as I’m quite positive that live_patch don’t trigger a new mount (gotta double check the docs on this one).

1 Like

That’s cool! I had never thought about adding it to the render and using a boolean. :smiley:

I think you might be right. After I click on the link generated with live_patch I get the error below:

[error] GenServer #PID<0.733.0> terminating
** (UndefinedFunctionError) function PentoWeb.WrongLive.handle_params/3 is undefined or private
    (pento 0.1.0) PentoWeb.WrongLive.handle_params(%{}, "http://localhost:4000/guess", #Phoenix.LiveView.Socket<assigns: %{answered_correctly: true, flash: %{}, live_action: nil, message: "You won! The number was 2", num: "2", score: -7}, changed: %{}, endpoint: PentoWeb.Endpoint, id: "phx-Fo7VKaxHCkAuCAGH", parent_pid: nil, root_pid: #PID<0.733.0>, router: PentoWeb.Router, transport_pid: #PID<0.729.0>, view: PentoWeb.WrongLive, ...>)

So I should probably implement the handle_params/3 function. Though I didn’t really get what it does. There’s not much of an explanation on the docs. For example if I implement it, it’s also invoked when I first navigate to the site, I think.

You thought well.
From the docs:

Generates a link that will patch the current LiveView.

When navigating to the current LiveView, Phoenix.LiveView.handle_params/3 is immediately invoked to handle the change of params and URL state. Then the new state is pushed to the client, without reloading the whole page while also maintaining the current scroll position. For live redirects to another LiveView, use live_redirect/2.

So when you click, handle_params is called and since you didn’t implement it yet, the click blows.

The handle_params does what the names suggest, it handles, well, parameters.
For your case, you just need the function without any other logic because you’re not actually dealing with any param, but you still need the function because live_patch will call it.
Something like:

def handle_params(_params, _url, socket) do
{:noreply, socket}
end

And you should get rid of the error you’re having.

If you look at the second line of your error message:
%{}, "http://localhost:4000/guess", #Phoenix.LiveView.Socket<...
You see that there are 3 elements here:
%{} => your _params on the handle_params/3 function.
If you had passed anything on your query string, they would show here.
http://localhost:4000/gues=> your _url
#Phoenix.LiveView.Socket => the socket that you’re passing back with the function on the form of the tuple {:noreply, socket}.

1 Like

But the problem now is that as you’d mentioned earlier this doesn’t trigger a new mount. :smiley:

After all I’ve decided to use live_redirect/2 which is a better solution in this case I think, so I can just keep reading the book as well. :smiley: Thank you all for the help!

2 Likes