phoenixframework / phoenix

Peace of mind from prototype to production
https://www.phoenixframework.org
MIT License
21.43k stars 2.88k forks source link

simple_form default for @as overrides form values #5901

Open rmoorman opened 3 months ago

rmoorman commented 3 months ago

While using the simple_form component (that is generated by default using mix phx.new), the following problem grabbed my attention. I was building a form schema inside a live view and wanted to render that form using the simple_form component.

The example states that it should be used like this

      <.simple_form for={@form} phx-change="validate" phx-submit="save">
        <.input field={@form[:email]} label="Email"/>
        <.input field={@form[:username]} label="Username" />
        <:actions>
          <.button>Save</.button>
        </:actions>
      </.simple_form>

But I thought, that it would be more consistent to use the <.simple_form :let={f} for={@form}> in my case (because I was likely to also use inputs_for in that project). But using :let does cause the struct f to contain nil as it's name, which in turn messes up the field names and value groupings of the form (as the prefix based on the schema name is missing).

Currently, the following code:

defmodule HelloWeb.SimpleFormLetBindingLive do
  use HelloWeb, :live_view

  defmodule FormSchema do
    use Ecto.Schema
    import Ecto.Changeset

    @primary_key false
    embedded_schema do
      field(:foo, :string)
      field(:bar, :string)
    end

    def changeset(data, attrs) do
      data
      |> cast(attrs, [:foo, :bar])
      |> validate_required([:foo, :bar])
    end
  end

  @impl Phoenix.LiveView
  def handle_params(_, _, socket) do
    form = to_form(FormSchema.changeset(%FormSchema{}, %{}))
    socket = assign(socket, form: form)
    {:noreply, socket}
  end

  @impl Phoenix.LiveView
  def render(assigns) do
    ~H"""
    <.simple_form :let={f} for={@form}>
      <table>
        <tr>
          <th><code>@form.name</code></th>
          <td><code><%= inspect(@form.name) %></code></td>
        </tr>
        <tr>
          <th><code>f.name</code></th>
          <td><code><%= inspect(f.name) %></code></td>
        </tr>
      </table>

      <.input field={@form[:foo]} label="Foo" />
      <.input field={f[:bar]} label="Bar" />
    </.simple_form>
    """
  end
end

produces the following results

readme-screenshot

Which seems odd to me.

I prepared an example repository that can be used to reproduce the issue.

josevalim commented 3 months ago

The issue is in the generated simple_form:

https://github.com/rmoorman/2024-phoenix-html-let-binding-issue/blob/master/lib/hello_web/components/core_components.ex#L192-L203

You can see it sets the default of @as to nil. You can change it in your app, I will migrate this to the Phoenix repo.

rmoorman commented 3 months ago

Thank you @josevalim for having a look!

I suppose the suggestion of using as is about working around the problem by adding as={@form.name} or something like that within render/1? (which does indeed work)

And if there is anything else I can do to help moving this forward, please let me know.