Form rendering problems with field {:array, :boolean}

I am trying to create a LiveView form that allows 6 checkboxs to be toggled and saved as a single array of boolean values under Ecto.

The problem is that every time I change the form by clicking on the checkboxes, this array grows and new empty fields are added to the LV. As far as I can tell, it looks like it is appending a value before the validate handle_event. I cannot figure out why (I have another field, {:array. :float} that works just fine in the same view. Otherwise, i have nothing but boilerplate in my live component file (nothing added to mount, update, etc)…

What the bug looks like:
2024-11-25 17-33-19

In my context:

defmodule AppName.Things.MyThing do
  use Ecto.Schema
  import Ecto.Changeset

  schema "things" do
    field :my_list, {:array, :boolean}, default: [false, false, false, false, false, false]
    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(my_thing, attrs) do
    my_thing
    |> cast(attrs, [
      :included
    ])
  end
end

In my LV:

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.simple_form
        for={@form}
        id="my_thing-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input
          :for={
            {entry, index} <-
              Enum.with_index(Ecto.Changeset.get_field(@form.source, :my_list, []))
          }
          value={entry}
          name="my_thing[my_list][]"
          type="checkbox"
        />
        <:actions>
          <.button phx-disable-with="Saving...">Save My Thing</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

Is this a problem from using :for on the checkbox input? I have tried a few things but they all felt hacky and did not work.

This fly.io blog covers how to do it pretty well. Can vouch for it: I’ve based my checkbox group components on it.

3 Likes

Thanks Frank! My mistake was definitely not looking more into the limitations of the checkbox input in the core_component. See How to make checkbox work with form bindings Phoenix LV? - #2 by LostKobrakai.

For future reference, this thread is also related: How to wire up a Phoenix form for a field {:array, :string}?

What I ended up with is an ugly ass solution that works:

  • A :check_options assign initiated with existing my_list values (if present)
  • A handrolled vanilla <input type="checkbox" ...> inside of a :for div
  • A phx-change handler to handle the checkboxes on/off and sanitize them into my temporary checked_options in my assign
  • And finally a a Map.merge in my save handler that take this checked_options and put it into my params prior to generating the changeset

So in other words, I am binding the data manually with handlers and making sure it is merged into the params/changeset rather than relying on the field={@form[:my_list]} convenience. A whole lot uglier but at least I can move on :saluting_face: