I am working my way through Programming Phoenix Live Views and I have this weird defect while working on the Promo codes in chapter 5. My solution works, but it has an error that triggers when you submit the form, then start typing again it immediately fails on attempts to validate because the form structure has changed in a way that makes the parameter matching no longer match.
Here’s the PromoLive module (the two handle event functions are important here):
defmodule PentoWeb.PromoLive do
alias Swoosh.Email.Recipient
use PentoWeb, :live_view
alias Pento.Promo
alias Pento.Promo.Recipient
def mount(_params, _session, socket) do
{:ok,
socket
|> assign_recipient()
|> clear_form()}
end
def handle_event(
"save",
%{"recipient" => recipient_params},
%{assigns: %{recipient: recipient}} = socket) do
changeset = recipient
|> Promo.change_recipient(recipient_params)
case Promo.send_promo(recipient) do
{:ok, _} ->
{:noreply, socket |> assign(trigger_submit: true) |> assign_form(changeset)}
{:error, _} ->
{:noreply, socket |> assign(check_errors: true) |> assign_form(changeset)}
end
{:noreply, assign_form(socket, %{})}
end
def handle_event(
"validate",
%{"recipient" => recipient_params},
%{assigns: %{recipient: recipient}} = socket) do
changeset =
recipient
|> Promo.change_recipient(recipient_params)
|> Map.put(:action, :validate)
{:noreply, assign_form(socket, changeset)}
end
def assign_recipient(socket) do
socket
|> assign(:recipient, %Recipient{})
end
def clear_form(socket) do
form =
socket.assigns.recipient
|> Promo.change_recipient()
|> to_form
assign(socket, :form, form)
end
def assign_form(socket, changeset) do
assign(socket, :form, to_form(changeset))
end
end
And the simple html that supports it.
<.header>
Send your promo code to a friend
<:subtitle>promo code for 10% off their first game purchase!</:subtitle>
</.header>
<div>
<.simple_form
for={@form}
id="promo_form"
phx-change="validate"
phx-submit="save">
<.input field={@form[:first_name]} type="text" label="First Name"/>
<.input field={@form[:email]} type="email" label="Email" phx-debounce="blur"/>
<:actions>
<.button phx-disable-with="Sending...">Send Promo</.button>
</:actions>
</.simple_form>
</div>
Like I said, this all works fine, but the second I type first name in after submission it crashes and restarts with the following error in the console.
[error] GenServer #PID<0.42461.0> terminating
** (FunctionClauseError) no function clause matching in PentoWeb.PromoLive.handle_event/3
lib/pento_web/live/promo_live.ex:14: PentoWeb.PromoLive.handle_event("validate", %{"_target" => ["first_name"], "_unused_email" => "", "email" => "", "first_name" => "J"}, #Phoenix.LiveView.Socket<id: "phx-F-iJ5MVWcMOHEBai", endpoint: PentoWeb.Endpoint, view: PentoWeb.PromoLive, parent_pid: nil, root_pid: #PID<0.42461.0>, router: PentoWeb.Router, assigns: %{form: %Phoenix.HTML.Form{source: %{}, impl: Phoenix.HTML.FormData.Map, id: nil, name: nil, data: %{}, action: nil, hidden: [], params: %{}, errors: [], options: [], index: nil}, __changed__: %{}, current_user: #Pento.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 1, email: "james.r.carr@gmail.com", confirmed_at: nil, inserted_at: ~U[2024-08-01 17:28:18Z], updated_at: ~U[2024-08-01 17:28:18Z], ...>, flash: %{}, live_action: nil, recipient: %Pento.Promo.Recipient{first_name: nil, email: nil}}, transport_pid: #PID<0.42454.0>, ...>)
This is due to the event recieving
%{"_target" => ["first_name"], "_unused_email" => "", "email" => "", "first_name" => "J"}
Instead of the normal %{"recipient" => %{}}
map. What causes this behavior?