Trying to add dynamic inputs_for inside a form_for

Hello all

I am fairly new to phoenix and need some guidance regarding my code.

I have a code in LiveView phoenix which has a template with a form_for and some fields. Inside the form i have a inputs_for field which has some data which i want to add dynamically in the view. I am not using changeset here. Here is my code.

LiveView template file

... other form_for fields
<h1>Enter new event details</h1>
<br>
<div class="row">
  <div class="column left">
      <label>Title</label> <br>
      <%= text_input f, :title %>
      <%= error_tag f, :title%>
      <br><br>

      <label>Name</label> <br>
      <%= text_input f, :name %>
      <%= error_tag f, :name %>
      <br><br>

  </div>
  <div class="column middle">
      <p class="mt-4 mb-2 font-bold">Options</p>
          <%= inputs_for f, :options, fn v -> %>
            <%= for option <- @options do %>
              <%= hidden_input v, :id, value: option["id"]%>
              <%= error_tag v, :id %>
              <%= checkbox v, :correct_answer %>
              <%= error_tag v, :correct_answer %>
              <%= text_input v, :answer, class: "form-control" %>
              <%= error_tag v, :answer %>
              <button phx-click="delete_option">x</button>
              <br><br>
            <% end %>
            <div class="inner"><button type="submit" href="#" phx-click="add-option">Add option</button></div>
          <% end %>

      <br><br>
  </div>
   ... other form_for fields

</div>
<% end %>
<br>
<%= submit "Submit", class: "btn" %>
</form>

I am sending the options array in the template as a blank array inside my mount function for this liveview. Like this:

def mount(_params, params, socket) do
    {:ok,
      assign(socket,
        options: [],
        ... other data passed through mount
      )}
end

I am able to create a new options field and update it on the UI. But when i click on submit i only get the first option which was added by the user and not all the ones which were created.

Similarly when I try to delete an option it deletes the option from the array I am maintaining but gives problems on the UI.

My handle_event(“add-option”):

def handle_event("add-option", params, socket) do
    size_of_options = length(socket.assigns.options)
    options = socket.assigns.options
    IO.inspect params

    option = %{"id" => size_of_options+1}
    options = [option | options]

    socket = socket
    |> assign(options: options)

    {:noreply, socket}
end

My handle_event(“delete_option”)

def handle_event("delete_option", params, socket) do
    options_updated = Enum.reject(socket.assigns.options, fn item -> "#{item["id"]}" == params["option_id"] end)
    socket = socket
    |> assign(options: options_updated)

    {:noreply, socket}
end

I am confused in the part where I want to click on submit and it should give me all the options which the user has created, and not just the first option.

Any help would be appreciated.

When inputs_for is correctly configured, it loops over the list of entries and numbers them appropriately. Check the HTML that’s being generated from the code above - I’d expect it to be producing duplicate field names.

inputs_for used with a Plug.Conn requires a hint to indicate “this field is repeating”, a default that’s a list.

Note that error_tag depends on form.errors, which is only populated by the FormData implementation for changesets.

can you point to my code where i am doing something wrong? thanks!

i checked the rendered html. it is causing duplicates due to which only the first element created is returned. how can i fix this?

The main issue is here:

inputs_for handles correctly naming the fields and doing the loop, so having to write a for loop inside is an indication that things aren’t set up right.

Saying inputs_for f, ... is going to look for that data on whatever’s being passed to form_for to build f. If you’re passing a Plug.Conn, you’ll need to have your handle_event code put the data in the right place in params for that to work. Also note that if you’re passing a conn to form_for, you’ll NEED to supply inputs_for with default: [] to tell it that options is a list of things.

I’d encourage you to reevaluate the “not using a changeset” position here; the form helpers, especially for nested data, will save you a lot of headaches if you supply them with better-structured input.

1 Like