elixirphoenix-live-view

No function matching clause error when handling event


I have the following code in my simple LiveView form.

defmodule TodoappWeb.UserRegistrationLive do
  alias Todoapp.Accounts
  use TodoappWeb, :live_view
  alias Todoapp.User

  @impl true
  def render(assigns) do
    ~H"""
    <.form :let={f} id="registration-form" for={@form} phx-submit="save" phx-change="validate">
      <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
        Register
      </h2>
      <.input type="text" field={f[:firstname]} label="Firstname" />

      <.input type="text" field={f[:middlename]} label="Middlename" />
      <.input type="text" field={f[:lastname]} label="Lastname" />

      <.input type="text" field={f[:email]} label="Email" />

      <br />
      <button
        type="submit"
        class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
      >
        Register
      </button>
    </.form>
    <br />
    <.link patch={~p"/users/login"}>Login</.link>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    form =
      %User{}
      |> User.changeset(%{})
      |> to_form(as: "form")

    {:ok, assign(socket, form: form)}
  end

  @impl true
  def handle_event("save", %{"form" => params}, socket) do
    case Accounts.register_user(params) do
      {:ok, _user} ->
        {:noreply,
         socket
         |> put_flash(:info, "User registered successfully!")
         |> redirect(to: ~p"/users/login")}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply,
         socket
         |> put_flash(:error, "Registration failed. Please check the errors below.")
         |> assign(form: to_form(changeset))}
    end
  end

  @impl true
  def handle_event("validate", %{"form" => params}, socket) do
    form =
      %User{}
      |> User.changeset(params)
      |> Map.put(:action, :validate)
      |> to_form(as: "form")

    {:noreply, assign(socket, form: form)}
    # {:noreply, assign(socket, params: params)}
  end
end

Form renders fine and I see error messages when the fields are empty, but if I enter a value in a field and delete it the form crashes with the following error:

[error] GenServer #PID<0.5544.0> terminating
** (FunctionClauseError) no function clause matching in TodoappWeb.UserRegistrationLive.handle_event/3
    (todoapp 0.1.0) lib/todoapp_web/live/user_registration_live.ex:44: TodoappWeb.UserRegistrationLive.handle_event("validate", %{"_target" => ["user", "lastname"], "user" => %{"email" => "", "firstname" => "d", "lastname" => "", "middlename" => ""}}, #Phoenix.LiveView.Socket<id: "phx-GAnDLL8TB_56nzlF", endpoint: TodoappWeb.Endpoint, view: TodoappWeb.UserRegistrationLive, parent_pid: nil, root_pid: #PID<0.5544.0>, router: TodoappWeb.Router, assigns: %{form: %Phoenix.HTML.Form{source: #Ecto.Changeset<action: :insert, changes: %{firstname: "d", lastname: "d"}, errors: [email: {"can't be blank", [validation: :required]}], data: #Todoapp.User<>, valid?: false, ...>, impl: Phoenix.HTML.FormData.Ecto.Changeset, id: "user", name: "user", data: %Todoapp.User{__meta__: #Ecto.Schema.Metadata<:built, "users">, id: nil, firstname: nil, middlename: nil, lastname: nil, email: nil, inserted_at: nil, updated_at: nil}, action: :insert, hidden: [], params: %{"email" => "", "firstname" => "d", "lastname" => "d", "middlename" => ""}, errors: [email: {"can't be blank", [validation: :required]}], options: [method: "post"], index: nil}, __changed__: %{}, flash: %{"error" => "Registration failed. Please check the errors below."}, live_action: :new}, transport_pid: #PID<0.5537.0>, ...>)

Not sure what I'm doing wrong?


Solution

  • You are using to_form inconsistently.

    In one place you call it with extra arg as: "form" but in other places you omit this arg.

    This :as option changes the shape of your form.

    Just add as: "form" to everywhere you are calling to_form and it will be fixed.