How to manually add a change to changeset without wiping the liveview form?

I have three buttons that call a handle_event in my liveview. I want to set a custom value to the changeset in the socket.assigns and it works. However the changeset.changes value only has valid values, which means the form is wiped of invalid values making a jarring experience for the end user.

def handle_event("change-listing-type", %{"listing-type-id" => listing_type_id}, socket) do
  changes =
    socket.assigns.listing_changeset.changes
    |> Map.put(:listing_type_id, listing_type_id)

  socket =
    socket
    |> assign(:listing_changeset, Listings.change_listing(%Listing{}, changes))

  {:noreply, socket}
end

Can I set this custom value on click without wiping away the invalid values?

Here’s my heex template if it helps.

<.form_hidden_input form={f} field={:listing_type_id} />
<div class="form-group">
  <label for="propertyType" class="form-label mb-2">Tipo de listado</label>
  <div class="grid grid-cols-3 gap-6">
    <%= for listing_type <- @listing_types do %>
      <div
        id={"listing-type-#{listing_type.id}"}
        phx-click="change-listing-type"
        phx-value-listing-type-id={listing_type.id}
        phx-data
        class={
          "cursor-pointer bg-gray-100 rounded-lg font-medium text-center py-4 #{if @listing_changeset.changes.listing_type_id == listing_type.id, do: "ring-inset ring-2 ring-gray-800"}"
        }
      >
        <%= listing_type.name %>
      </div>
    <% end %>
  </div>
</div>

What you are looking for is put_change/3.

1 Like

Actually what I needed here was to save the form params to an assigns and use that on the custom handle_event otherwise the form would have been wiped.

Maybe there’s a better way to do this?

def mount(_params, _session, socket) do
  listing_types = Listings.list_listing_types()
  sale_type = listing_types |> List.first()

  listing_changeset =
    Listings.change_listing(%Listing{}, %{
      user_id: socket.assigns.current_user.id,
      listing_type_id: sale_type.id
    })

  socket =
    socket
    |> assign(:listing_changeset, listing_changeset)
    |> assign(:listing_types, listing_types)
    |> assign(:form_params, %{})

  {:ok, socket}
end

def handle_event("change-listing-type", %{"listing-type-id" => listing_type_id}, socket) do
  changes =
    socket.assigns.form_params
    |> KeyToAtom.key_to_atom()
    |> Map.put(:listing_type_id, listing_type_id)

  socket =
    socket
    |> assign(:listing_changeset, Listings.change_listing(%Listing{}, changes))

  {:noreply, socket}
end

def handle_event(
      "validate",
      %{"listing" => params},
      socket
    ) do
  listing_changeset =
    %Listing{}
    |> Listings.change_listing(params)

  {:noreply, assign(socket, listing_changeset: listing_changeset, form_params: params)}
end

image