phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.22k stars 931 forks source link

inputs_for not displaying nested inputs, cannot add or remove as well #2646

Closed adenobe closed 1 year ago

adenobe commented 1 year ago

Environment

Actual behavior

I was trying to replicate the example from @chrismccord Key note address: https://youtu.be/FADQAnq0RpA?t=2990, I expected the nested inputs to display but they don't except when I set the values upfront on the Changeset. Not sure what I am doing wrong

defmodule TryInputs.List do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "lists" do
    field :position, :integer
    field :title, :string

    embeds_many :notifications, EmailNotification, on_replace: :delete do
      field :email, :string
      field :name, :string
    end

    timestamps()
  end

  @doc false
  def changeset(list, attrs) do
    list
    |> cast(attrs, [:title, :position])
    |> validate_required([:title, :position])
    |> cast_embed(:notifications,
      with: &email_changeset/2,
      sort_param: :notifications_order,
      drop_param: :notifications_delete
    )
  end

  @doc false
  def email_changeset(email, attrs) do
    email
    |> cast(attrs, [:email, :name])
    |> validate_required([:email, :name])
  end
end

defmodule TryInputsWeb.ListLive do
  use TryInputsWeb, :live_view

  alias TryInputs.List
  alias TryInputs.Repo

  def render(assigns) do
    ~H"""
    <.simple_form for={@form} id="list" phx-change="validate" phx-submit="save">
      <.input field={@form[:title]} type="text" label="title" />
      <.input field={@form[:position]} type="text" label="position" />
      <h1>Invite order users</h1>
      <div id="notifications">
        <.inputs_for :let={f_nested} field={@form[:notifications]}>
          <div class="flex">
            <input type="hidden" name="list[notifications_order][]" value={f_nested.index} />
            <.input type="text" field={f_nested[:email]} />
            <.input type="text" field={f_nested[:name]} />
            <label class="cursor-pointer">
              <input
                type="checkbox"
                name="list[notifications_delete][]"
                value={f_nested.index}
                class="hidden"
              />
              <.icon name="hero-x-marked" /> remove
            </label>
          </div>
        </.inputs_for>
      </div>
      <label class="cursor-pointer">
        <input type="checkbox" name="list[notifications_order][]" class="hidden" />
        <.icon name="hero-plus-icon" />Add more
      </label>
      <:actions>
        <.button phx-disable-with="Saving...">Save Todo</.button>
      </:actions>
    </.simple_form>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    changeset  = change_list(%List{})

    {:ok,
     socket
     |> assign(:page_title, "New List")
     |> assign_form(changeset)}
  end

  @impl true
  def handle_event("validate", _payload, socket) do
    {:noreply, socket}
  end

  @impl true
  def handle_event("save", %{"list" => list_params}, socket) do
    %List{}
    |> List.changeset(list_params)
    |> Repo.insert()
    |> IO.inspect()

    {:noreply, socket}
  end

  defp assign_form(socket, %Ecto.Changeset{} = changeset) do
    assign(socket, :form, to_form(changeset))
  end

  defp change_list(%List{} = list, attrs \\ %{}) do
    List.changeset(list, attrs)
  end
end

#mix file deps
  defp deps do
    [
      {:phoenix, "~> 1.7.2"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto_sql, "~> 3.10.1"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 3.3"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.18.18"},
      {:floki, ">= 0.30.0", only: :test},
      {:esbuild, "~> 0.7", runtime: Mix.env() == :dev},
      {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},
      {:swoosh, "~> 1.3"},
      {:finch, "~> 0.13"},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.20"},
      {:jason, "~> 1.2"},
      {:plug_cowboy, "~> 2.5"}
    ]
  end

Expected behavior

I expected the nested inputs to display as explained on that video. I also expect to be able to remove and add nested fields as well

They only show when I do something like

    changeset =
      change_list(%List{
        notifications: [
          %List.EmailNotification{email: "email@me.com", name: "me"},
          %List.EmailNotification{email: "email@you.com", name: "yo"}
        ]
      })
chrismccord commented 1 year ago

please make sure you are on latest ecto_sql and phoenix_live_view (mix deps.update both of them) and report back. I assume you mean that when you click "Add more" from the initial empty changeset nothing happens, right? This should have been fixed on ecto_sql, so please give that a try. Thanks!

adenobe commented 1 year ago

@chrismccord Yes, I am on the latest ecto_sql (3.10.1) and phoenix_live_view (0.18.18).

Not only "add more", the nested inputs don't display at all and remove field does not work as well

Terminal shows like below but not changes

HANDLE EVENT "validate" in TryInputsWeb.ListLive
  Parameters: %{"_target" => ["list", "notifications_order"], "list" => %{"notifications" => %{"0" => %{"email" => "email@me.com", "name" => "me"}, "1" => %{"email" => "email@you.com", "name" => "yo"}}, "notifications_delete" => ["0"], "notifications_order" => ["0", "1", "on"], "position" => "", "title" => ""}}
adenobe commented 1 year ago

Please @dfalling how were you able resolve this?

josevalim commented 1 year ago

Your “validate” is not doing anything. Please Make sure it creates a new changeset with the params.

adenobe commented 1 year ago

Thanks @josevalim That fixed it