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.
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 %>
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).
That’s cool! I had never thought about adding it to the render and using a boolean.
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.
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}.
But the problem now is that as you’d mentioned earlier this doesn’t trigger a new mount.
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. Thank you all for the help!