ashrafhasson

ashrafhasson

Hello All,

I have a customer schema with nested contacts that’s defined as an embedded schema that I render in a liveview form component. I’m trying to add and delete the nested contact dynamically. Adding works just fine but when I trigger a remove action, I can’t see the param for the newly added contact in the handle_event callback .. not sure how to go about getting this to work properly, any advice on what I’m doing wrong here please?

defmodule MyApp.Customer do
  use Ecto.Schema
  import Ecto.Changeset

  schema "customers" do
    field :name, :string
    embeds_many :contacts, MyApp.ContactDetail

    timestamps()
  end

  @doc false
  def changeset(customer, attrs) do
    customer
    |> cast(attrs, [:name])
    |> cast_embed(:contacts)
    |> validate_required([:name])
  end
end

defmodule MyApp.ContactDetail do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field :temp_id, :string, virtual: true
    field :contact_type, Ecto.Enum, values: [:address, :phone, :email]
    field :details, :string
  end

  def changeset(embedded_schema, attrs) do
    embedded_schema
    |> cast(attrs, [:contact_type, :details, :temp_id])
    |> validate_required([:contact_type, :details])
  end
end

In my customer_live/form_component.html.heex, I have something like this for the contacts section:

  <.form
    let={f}
    for={@changeset}
    id="customer-form"
    phx-target={@myself}
    phx-change="validate"
    phx-submit="save">

    <%= for c <- inputs_for(f, :contacts) do %>
	    <%= hidden_inputs_for(c) %>
            <%= text_input c, :details %>
            <%= hidden_input(c, :temp_id) %>
            <a href="#"
               phx-target={@myself}
               phx-click="remove-contact"
               phx-value-contact={c.data.temp_id}>&times</a>
    <% end %>

  </.form>

In the customer_live.form_component, the handle_event("remove-contact", %{"contact" => temp_id}, socket) gets called but the temp_id is empty. I tried referencing it like c.temp_id but that blows up with an error.

  def handle_event("add-contact", _, socket) do
    existing_contacts = Map.get(socket.assigns.changeset.changes, :contacts, [])

    new_contacts = [%{temp_id: :rand.uniform(1000)} | existing_contacts]
    changeset = Ecto.Changeset.put_embed(socket.assigns.changeset, :contacts, new_contacts)

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

  def handle_event("remove-contact", %{"contact" => contact_id}, socket) do
    new_contacts = socket.assigns.changeset.changes
    |> Map.get(:contacts)
    |> Enum.reject(&(&1.changes.temp_id == contact_id))

    changeset = Ecto.Changeset.put_embed(socket.assigns.changeset, :contacts, new_contacts)
    {:noreply, assign(socket, changeset: changeset)}
  end

I’m using liveview 0.17.5 btw.

Appreciate any help, thanks!

Showing Posts 1 to 5

f0rest8

f0rest8

Quick thought is that the field you’re trying to match on to delete is a virtual field so it doesn’t exist when accessing it. Have you tried matching on the :id rather than the :temp_id for deleting it?

ashrafhasson

ashrafhasson OP

@f0rest8 my understanding is that a virtual field should exist but is just not persisted. I tried the :id field but that seems to reference the Phoenix.HTML.Form interpolated id which is sent back as "customer-form_contacts_0", I have no reference to that before the form is rendered. This is why I thought using a :temp_id set to a random value is maybe a good approach ..

ashrafhasson

ashrafhasson OP

and if I try c.data.id I get an empty %{} value back :frowning:

f0rest8

f0rest8

Yea I’m not totally sure but when I’ve used an embedded schema in the form, I did something like this…

<.form let={f} for={@changeset} id="..." phx-target={@myself} phx-change="validate" phx-save="submit">
  <%= for f_item <- inputs_for(f, :line_items) do %>
  ...
    <.button type="button" 
      phx-click="delete-line-item" 
      phx-value-index={f_item.index} 
      phx-target={@myself}>Delete</.button>
  ...
</.form>

# Handle event
def handle_event("delete-line-item", %{"index" => index}, socket} do
  ...
end
ashrafhasson

ashrafhasson OP

I was able to get over this issue by utilizing the value of the hidden input field for :temp_id :smiley:. Not sure why I’ve not thought of using that lol

@f0rest8 this is very similar to what you do with referencing the index but I used the value like so:

<%= hidden_input(c, :temp_id) %>
<a href="#"
    phx-target={@myself}
    phx-click="remove-contact"
    phx-value-contact={input_value(c, :temp_id)}>&times</a>
— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews