(KeyError) key :name not found in

I’ve been playing with Phoenix for a while now and I’ve encountered a problem that I simply don’t understand, so I decided to ask for help.

The entirety of the code base can be found here

The core of the issue is here, the error:

Request: GET /users/two_factor
** (exit) an exception was raised:
    ** (KeyError) key :name not found in: %{__changed__: nil, __given__: %{__changed__: nil, field: {%Phoenix.HTML.Form{source: :user, impl: Phoenix.HTML.FormData.Atom, id: "verify-2fa-auth", name: "user", data: %{}, hidden: [], params: %{}, errors: [], options: [as: nil, id: "verify-2fa-auth", multipart: false, "phx-submit": "verify"], index: nil, action: nil}, :verification}, type: "text"}, errors: [], field: {%Phoenix.HTML.Form{source: :user, impl: Phoenix.HTML.FormData.Atom, id: "verify-2fa-auth", name: "user", data: %{}, hidden: [], params: %{}, errors: [], options: [as: nil, id: "verify-2fa-auth", multipart: false, "phx-submit": "verify"], index: nil, action: nil}, :verification}, id: nil, inner_block: [], label: nil, multiple: false, prompt: nil, rest: %{required: true}, type: "text"}
        (another_test 0.1.0) lib/another_test_web/components/core_components.ex:399: anonymous fn/2 in AnotherTestWeb.CoreComponents."input (overridable 1)"/1
        (another_test 0.1.0) /home/bmorriso/local-repo/livesaastestagain/another_test/lib/another_test_web/live/user_two_factor_live.ex:24: AnotherTestWeb.UserTwoFactorLive.render/1
        (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:383: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:537: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
        (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:383: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:537: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
        (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:383: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:537: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
        (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:383: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:537: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
        (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:383: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.1) lib/phoenix_live_view/diff.ex:537: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
        (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3

The code for this view: (Full source code here defmodule)

      <.simple_form :let={f} for={:user} id="verify-2fa-auth" phx-submit="verify">
        <.input field={{f, :verification}} type="text" required />
        <:actions>
          <.button class="w-full" phx-disable-with="Sending...">Verify</.button>
        </:actions>
      </.simple_form>

The core_components.ex (full source code here)

  def input(assigns) do
    ~H"""
    <div phx-feedback-for={@name} class="space-y-2">
      <.label for={@id}><%= @label %></.label>
      <input
        type={@type}
        name={@name}
        id={@id || @name}
        value={Phoenix.HTML.Form.normalize_value(@type, @value)}
        class={[
          "block w-full p-2.5 bg-gray-50 text-sm rounded-lg border border-gray-300 text-gray-900",
          "focus:ring-4 focus:ring-blue-500/5 focus:border-blue-500",
          "dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500/5 dark:focus:border-blue-500",
          "phx-no-feedback:border-gray-300 phx-no-feedback:focus:border-blue-500 phx-no-feedback:focus:ring-blue-500/5",
          "phx-no-feedback:dark:border-gray-600 phx-no-feedback:dark:focus:border-blue-500 phx-no-feedback:dark:focus:ring-blue-500/5",
          @errors != [] &&
            "text-red-900 placeholder-red-700 border-red-500 focus:ring-red-500 focus:border-red-500 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500"
        ]}
        {@rest}
      />
      <.error :for={msg <- @errors}><%= msg %></.error>
    </div>
    """
  end

Thank you for your time.

Following error you have not passed name value.

<.input field={{f, :verification}} type="text" name="some-name" required />

It should not be necessary to pass the name, as it could be deduced from the field name.

  def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    assigns
    |> assign(field: nil, id: assigns.id || field.id)
    |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
    |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
    |> assign_new(:value, fn -> field.value end)
    |> input()
  end

But You are using an atom… for={:user}

This should not be allowed anymore to pass a single atom.

You should pass an empty changeset, and use for={…} as={:user}

…or set the name attribute manually

  @doc """
  Renders an input with label and error messages.

  A `%Phoenix.HTML.Form{}` and field name may be passed to the input
  to build input names and error messages, or all the attributes and
  errors may be passed explicitly.

  ## Examples

      <.input field={@form[:email]} type="email" />
      <.input name="my-input" errors={["oh no!"]} />
  """
1 Like

It can’t seem to deduce from the field name. But explicitly assigning name="verification" does work, but it now complains about “value” not existing

key :value not found in: %{__changed__: nil, __given__: %{__changed__: nil, field: {
            :verification
        }, name: "verification", type: "input"
    }, errors: [], field: {
        :verification
    }, id: nil, inner_block: [], label: nil, multiple: false, name: "verification", prompt: nil, rest: %{required: true
    }, type: "input"
}

I thought this would be deduced from what was submitted in the input field, but doesn’t seem to be the case.

This is how I do when I don’t have a changeset, previously I would just pass an atom, but now…

  def mount(_params, _session, socket) do
    {
      :ok,
      socket
      |> assign(:graph_code, nil)
      |> assign_form(%{})
    }
  end

...

  defp assign_form(socket, changeset) do
    assign(socket, :form, to_form(changeset))
  end

and in the form

    <.simple_form for={@form} as={:form} id="form-graph" class="form-floating flex-1" phx-submit="save">
        <.input field={@form[:graph]} type="textarea" rows="30" cols="40" nolabel placeholder="Enter Graph code"
            hint="Enter Graph code" value={@graph_code} />
        <:actions>
            <.button phx-disable-with={gettext("Saving...")} class="btn-primary">Submit Graph</.button>
        </:actions>
    </.simple_form>

As a warning, I do not use the standard core_components.ex, but one I did for Bootstrap 5, and You might see unknown (custom) attributes, eg: nolabel :slight_smile: