Mapping over an array in a from shows no results

Hi,

I have a form where a user can add items to an array.
When I’m trying to map over it and show the fields, it displays nothing, even though the form gets updated.

Here’s my mount -

  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(
        form:
          to_form(%{
            "title" => "list a",
            "category" => "movies",
            "description" => "",
            "items" => ["3"]
          })
      )
      |> assign(:current_section, :new_list)
      |> assign(:new_item, "")

    {:ok, socket}
  end

I tried several approaches -

      <%= for {item, index} <- Enum.with_index(@form[:items].value || []) do %>
        <div>
          <p>Item <%= index + 1 %>: <%= item %></p>
          <input type="text" name={"items[#{index}]"} value={item} />
        </div>
      <% end %>

This prints nothing.

      <.inputs_for :let={f_items} field={@form[:items]}>
        <.input type="text" field={f_items[:item]} />
      </.inputs_for>

This one errors with -
construction of binary failed: segment 1 of type ‘binary’: expected a binary but got: nil

I followed the docs, but something’s off.

The idea of the form is that a user can add more items by clicking the + icon.
That’s the element I want to be created -

<div class="mt-2 flex gap-4 justify-center items-center">
          <div class="grow">
            <.input_on_light
              field={@form[:item]}
              type="text"
              placeholder="Item 1"
              autocomplete="off"
              required
              class="grow block w-full rounded-md border-0 bg-white/5 py-1.5 text-white shadow-sm ring-1 ring-inset ring-white/10 focus:ring-2 focus:ring-inset focus:ring-indigo-500 sm:text-sm sm:leading-6"
            />
          </div>
          <button type="button" phx-click="add_item" class="shrink grow-0 w-40">
            <.icon name="hero-plus" />
          </button>
</div>

Would love a hint, thank you

Hello and welcome to Elixir Forum!

This seems to be a very common beginner hurdle. inputs_for is what you’re looking for. It also explains dynamically adding and removing children. This is also a great article on the subject and still a viable way to do things, though perhaps stick to the docs to start and that article can help provide more insight into the %Form{} struct.

1 Like

Hey,

Thank you for your reply, as mentioned in the post, I’ve tried inputs_for, can’t make it work, read quite a lot of guides on that, wouldn’t have posted otherwise.
One thing to note though is that I’m not using Ecto in this project, and every ref I’ve found talks about Ecto. Don’t know if related, just pointing out.

D’oh, I’m so sorry. This question comes up a lot so I didn’t read carefully enough.

I don’t much experience building forms with bare maps, but your "items" key should be a list of maps representing your items. A list of strings isn’t going to work.

socket
|> assign(
  form:
    to_form(%{
      "title" => "list a",
      "category" => "movies",
      "description" => "",
      "items" => [%{
        "name" => "Item Name"
      }]
    })
)

When you want to add a new item, you need to “build” it, ie, append (or prepend) it to the nested "items" list.

If you take a look at that article I linked, it should give you some clues. When appending, form.source should be a bare map as opposed to a changeset.

I don’t have time to play around with it now but if you still haven’t figured it out I can give it a shot as I’m never played around with nested bare map forms this way so I’m cursious.