Phx-click inside form clears other fields

I’m working on a small project on my spare time.

In the project I have a view generated with phx.gen.live and in the form component I’m trying to add a map and fetching coordinates from Mapbox.

When the user types things in the input for the address I search for corresponding addresses and present suggestions. When the user clicks on one address I want to set the address and the coordinates.
But when the user clicks the address the description is cleared.

The fields in my changeset are:
Address
Description
Lat
Long

This is my FormComponent. I guess I am doing something wrong when I handle the phx-click event but I can’t see what.

defmodule ExampleWeb.TableLive.FormComponent do
  use ExampleWeb, :live_component

  alias Example.TestEvent

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage table records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="table-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:address]} type="text" label="Address" phx-debounce="1000" />
        <ul class="z-10 absolute bg-white/75 w-full">
          <%= for %{name: name, id: id} <- @suggestions do %>
            <li phx-target={@myself} phx-click="select_address" phx-value-id={id}>  <%= name %> </li>
          <% end %>
        </ul>
        <div phx-hook="Map" id='map' class="rounded relative aspect-square w-full"
            style="aspect-ratio: 1 / 1; width: 100%; height: 300px" phx-update="ignore"></div>
        <.input field={@form[:description]} type="textarea" label="Description" />
        <:actions>
          <.button phx-disable-with="Saving...">Save table</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

  @impl true
  def update(%{table: table} = assigns, socket) do
    changeset = TestEvent.change_table(table)

    {:ok,
     socket
     |> assign(assigns)
     |> assign_form(changeset)}
  end

  @impl true
  def handle_event("validate", %{"_target" => ["table", "address"], "table" => %{"address" => _address} = table_params}, socket) do
    url = "https://1902d58c-e651-4162-a087-6564338cf4fc.mock.pstmn.io/mock_mapbox"
    response = HTTPoison.get!(url)
    {:ok, body} = Poison.decode(response.body)
    suggestions = body["features"]
    |> Enum.map(fn %{"place_name" => name, "geometry" => %{"coordinates" => cords}, "id" => id} -> %{name: name, cords: cords, id: id} end)

    changeset = socket.assigns.table
    |> TestEvent.change_table(table_params)
    |> Map.put(:action, :validate)

    socket = socket
    |> assign_form(changeset)
    |> assign(:suggestions, suggestions)
    {:noreply, socket}
  end

# this function mocks the call to map box that searches addresses
  @impl true
  def handle_event("select_address", params, socket) do
    selected = socket.assigns.suggestions |> Enum.filter(fn s -> s.id == params["id"] end) |> hd
    formatted_coords = %{lng: Enum.at(selected.cords, 0), lat: Enum.at(selected.cords, 1)}

    table_params = %{"address" => selected.name, "lng" => formatted_coords.lng, "lat" => formatted_coords.lat}
    changeset = socket.assigns.table
    |> TestEvent.change_table(table_params)
    |> Map.put(:action, :validate)

    socket = socket
    |> assign_form(changeset)
    |> assign(coords: formatted_coords, suggestions: [])
    {:noreply, assign(socket, coords: formatted_coords, suggestions: []) |> push_event("position", formatted_coords)}
  end

  @impl true
  def handle_event("validate", %{"table" => table_params}, socket) do
    changeset =
      socket.assigns.table
      |> TestEvent.change_table(table_params)
      |> Map.put(:action, :validate)

    {:noreply, assign_form(socket, changeset)}
  end

  def handle_event("save", %{"table" => table_params}, socket) do
    save_table(socket, socket.assigns.action, table_params)
  end

  defp save_table(socket, :edit, table_params) do
    case TestEvent.update_table(socket.assigns.table, table_params) do
      {:ok, table} ->
        notify_parent({:saved, table})

        {:noreply,
         socket
         |> put_flash(:info, "Table updated successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp save_table(socket, :new, table_params) do
    case TestEvent.create_table(table_params) do
      {:ok, table} ->
        notify_parent({:saved, table})

        {:noreply,
         socket
         |> put_flash(:info, "Table created successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp assign_form(socket, %Ecto.Changeset{} = changeset) do
    socket = socket
    |> assign(:form, to_form(changeset))
    case Map.has_key?(socket.assigns, :suggestions) do
      false -> assign(socket, :suggestions, [])
      _ -> socket
    end
  end

  defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end

I accidentally deleted my post when I wanted to edit it.

Looks like there’s a typo in the handle_event callback for "select_address" events – specifically a missing letter in address.

Oh, that’s my swedish-english translation miss. The :adress assign is not used in the form.

Have you tried inserting an IO.inspect into this callback? I’d suggest adding an additional step to the tailend of the changeset pipeline that calls IO.inspect to identify if the description field gets inadvertently reassigned in the callback.

There’s also a redundant assigning of coords and suggestions to the socket at the moment.

The other spot that seems worth inspecting is the form assign here:

Taking another look, it seems that unlike in the “validate” event callback, you’re not receiving the entire table params in the “select_address” callback and since the updated description is only in socket.assigns.form and not in socket.assigns.table, the description field gets lost when you reassign the form with a changeset built off of socket.assigns.table.

I found the problem.
When I update the changeset I need to add all the parameters from the form

    changeset = socket.assigns.table
    |> IO.inspect
    |> TestEvent.change_table(Map.merge(socket.assigns.form.params, table_params))
    |> Map.put(:action, :validate)
    |> IO.inspect
1 Like