Duplicate entry in form/inputs_for

I’m loving the new drop_param and sort_param options for input forms. However, I’m finding myself trying to implement a duplication button.

Here’s what my current implementation looks like:

I can move the entries around using the three bars on the left (using sort_param, delete entries using the X on the right (using drop_param), and now I want to be able to duplicate entries using the last action on the right.

Is there an easy way to do that? Preferably, the new entry should appear just below the duplicated entry, pushing other entries down, but a strategy that just inserts a duplicate at the end could also work.

My first idea was to implement the duplication button using phx-click, like this (where entry is the bound value from an inputs_for):

<span
  class="hero-document-duplicate"
  phx-click="duplicate"
  phx-value-entry_id={entry.index}
/>

And then use manipulate form.params in my handler, like so:

  def handle_event("duplicate", %{"entry_id" => entry_id}, socket) do
    form_params = socket.assigns.form.params

    form_params =
      update_in(
        form_params["entries"],
        fn entries ->
          entries
          |> Enum.map(fn {k, v} ->
            {if String.to_integer(k) > String.to_integer(entry_id) do
               to_string(String.to_integer(k) + 1)
             else
               k
            end, v}
          end)
          |> Map.new()
          |> Map.put(
            to_string(String.to_integer(entry_id) + 1),
            Map.delete(form_params["entries"][entry_id], "id")
          )
        end
      )
    
    ...
  end

However, in addition to looking extremely ugly, it doesn’t work because form.params is not always set (i.e. if I duplicate a row before making any other change in the form). On the other hand, it seems unwise to just use form.data directly, since it won’t have in-progress changes.

How would you go about implement duplication in a form like this?

Short answer: Use Ecto.Changeset.get_assoc.

Longer answer would be found here: One-to-Many LiveView Form | Benjamin Milde