Why are phx-value values returned as strings in handle_event?

This is from the exercise of Phoenix Liveview. Can someone tell me why is guess a string? (the guess is in the handle_event function) And how to make it a number instead? Thank you

defmodule PentoWeb.WrongLive do
  use PentoWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, assign(
      socket, 
      score: 0,
      message: "Make a guess:",
      time: time(),
      answer: rand_num()
      )
    }
  end

  @spec render(any) :: Phoenix.LiveView.Rendered.t()
  def render(assigns) do
  ~H"""
    <h1>Your score: <%= @score %></h1>
    <h2>
      <%= @message %>
      It's <%= @time %>
      The answer is <%= @answer %>
    </h2>
    <h2>
      <%= for n <- 1..10 do %>
        <.button phx-click="guess" phx-value-number= {n} >
          <%= n %>
        </.button>
      <% end %>
    </h2>
  """
  end
  
  def time() do
    DateTime.utc_now |> to_string
  end

  def rand_num() do
    Enum.random(0..10)
  end

  def handle_event("guess", %{"number" => guess}, socket) do
    time = time()
    answer = socket.assigns.answer 
    guess_num = String.to_integer(guess) #IDK, it is fucking string

    if (guess_num == answer) do
      message = "Congratuations! Your guess: #{guess} is correct. "
      score = socket.assigns.score + 1
      {
        :noreply,
        assign(
          socket,
          message: message,
          score: score,
          time: time,
          answer: answer
        )
      }
    else
      message = "Your guess: #{guess}. Wrong. Guess again. "
      score = socket.assigns.score - 1
      {
        :noreply,
        assign(
          socket,
          message: message,
          score: score,
          time: time,
          answer: answer
        )
      }
    end
  end
end

To understand why, look at the HTML. HTML attributes take the form attribute="value", so all(?) are strings. Often they need to be parsed to a number.

You either convert it to a number in handle_event like you’re doing or you use phx-click={JS.push("guess", value: %{number: n})}.

I have a module called Ensure which has functions like Ensure.integer, Ensure.datetime which I use for a lot of my params. They will cast it if it is a string or pass it straight through if it is of the desired type.

4 Likes

Ecto.Type.cast or a schemless changeset can also be used to handle typecasting of params.

5 Likes

I see, didn’t know it’s a prevalent thing in phoenix, I guess it makes sense.
Thanks for the pointers.

There‘s no way around this for values, which are encoded in html, sent to the client and sent back by the client eventually.

1 Like