Liveview Form - Update other fields on phx-change

Hi!
I would like to ask for help. I have a form and I would like to change the values of some fields if I change one of them.
For example, I have this code snippet:

@impl true
  def handle_event("validate", %{"_target" => ["invoice", "date"], "invoice" => invoice_params}, socket) do
    %{"date" => date} = invoice_params
    invoice = %{
      socket.assigns.invoice
      | due_date: Date.add(Date.from_iso8601!(date), socket.assigns.invoice.payment_term),
        date: Date.from_iso8601!(date)
    }

    changeset =
      invoice
      |> Invoices.change_invoice(invoice_params)
      |> Ecto.Changeset.put_assoc(:invoice_lines, invoice.invoice_lines)
      |> Map.put(:action, :validate)

    {:noreply,
     socket
     |> assign(:invoice, invoice)
     |> assign(:changeset, changeset)}
  end

I want to update the due_date field. But this does not change the field in the form.
I’ve manually updated the socket.assigns.invoice, it doesn’t work either.

I have a partner field which works though. When I select a partner, it goes through a few updates. The problem there is that the validation on the other field (date) does not work. Here is the code for that:

@impl true
 def handle_event(
     "validate",
     %{"_target" => ["invoice", "partner_id"], "invoice" => %{"partner_id" => partner_id}} = invoice_params,
     socket) when partner_id != "" do
   partner = Partners.get_partner!(partner_id) |> Repo.preload([:bank_accounts])
   invoice = %{
     socket.assigns.invoice
     | partner_id: partner.id,
       payment_method: partner.payment_method,
       payment_term: partner.payment_term,
       currency_id: partner.currency_id,
       due_date: Date.add(socket.assigns.invoice.date, partner.payment_term),
       exchange_rate: exchange_rate(partner.currency_id)
   }
   changeset = invoice_changeset(invoice, invoice_params) |> Map.put(:action, :validate)

   {:noreply,
    socket
    |> assign(:partner, partner)
    |> assign(:invoice, invoice)
    |> assign(:partner, partner)
    |> assign(:changeset, changeset)
    |> assign(:bank_accounts, bank_accounts(partner.bank_accounts))}
 end

Is there anything in the documentation about this, or perhaps an example? I have not found anything similar here on the forum. Thank you! :slight_smile:

2 Likes

Hmm, what does your HEEX code look like?

And as a sanity check, could you add an IO.inspect(changeset, label: "changeset: ") and post the console output? It also might be worth setting liveSocket.enableDebug() and sharign what comes through in your browser’s developer console.

1 Like

This might be a long shot, but could it be that the element in the form whose value you want to update is the one having focus? Because in that case LiveView would never overwrite the value entered by the user.

1 Like

Hi!
When i change partner which affect other fields:

Parameters: %{"_target" => ["invoice", "partner_id"], "invoice" => %{"currency_id" => "9425c106-1504-4ae5-a4f0-4dba1f10ae1f", "date" => "2023-03-06", "delivery_date" => "2023-03-06", "due_date" => "2023-03-06", "exchange_rate" => "1.0", "partner_id" => "2055bf2d-dd76-49be-ab93-b9837e184480", "payment_method" => "transfer", "payment_term" => "0"}}

changeset: #Ecto.Changeset<action: :validate, changes: %{}, errors: [],
 data: #Invoicer.Invoice.Invoices.Invoice<>, valid?: true>

Brower console:

{
    "3": {
        "1": {
            "0": {
                "0": " method=\"post\" errors=\"\" id=\"invoice-form\" phx-change=\"validate\" phx-submit=\"save\"",
                "1": "",
                "2": "",
                "3": {
                    "0": {
                        "2": {
                            "0": " phx-feedback-for=\"invoice[partner_id]\"",
                            "1": {
                                "0": " for=\"invoice-form_partner_id\"",
                                "1": {
                                    "0": "Partner"
                                }
                            },
                            "2": " id=\"invoice-form_partner_id\"",
                            "3": " name=\"invoice[partner_id]\"",
                            "5": "",
                            "7": "<option value=\"\"></option><option selected value=\"2055bf2d-dd76-49be-ab93-b9837e184480\">Székely Kft.</option>",
                            "8": ""
                        },
                        "3": {
                            "0": {
                                "0": " phx-feedback-for=\"invoice[bank_account_id]\"",
                                "1": {
                                    "0": " for=\"invoice-form_bank_account_id\"",
                                    "1": {
                                        "0": "Bank",
                                        "s": [
                                            "\n    <span class=\"label-text\">\n      ",
                                            "\n    </span>\n  "
                                        ]
                                    },
                                    "s": [
                                        "<label",
                                        " class=\"label\">\n  ",
                                        "\n</label>"
                                    ]
                                },
                                "2": " id=\"invoice-form_bank_account_id\"",
                                "3": " name=\"invoice[bank_account_id]\"",
                                "4": "",
                                "5": "",
                                "6": "",
                                "7": "<option value=\"bc830162-9b9c-4ba5-a0e7-4a4a8dd414af\">KH Bank Zrt.</option>",
                                "8": "",
                                "s": [
                                    "<div",
                                    ">\n  ",
                                    "\n  <select",
                                    "",
                                    " class=\"select select-primary w-full p-2 focus:ring-0 focus:border-primary active:ring-0\"",
                                    "",
                                    ">\n    ",
                                    "\n    ",
                                    "\n  </select>\n  ",
                                    "\n</div>"
                                ]
                            },
                            "s": [
                                "",
                                ""
                            ]
                        },
                        "4": {
                            "0": " phx-feedback-for=\"invoice[currency_id]\"",
                            "1": {
                                "0": " for=\"invoice-form_currency_id\"",
                                "1": {
                                    "0": "Currency"
                                }
                            },
                            "2": " id=\"invoice-form_currency_id\"",
                            "3": " name=\"invoice[currency_id]\"",
                            "5": "",
                            "7": "<option selected value=\"9425c106-1504-4ae5-a4f0-4dba1f10ae1f\">Forint</option><option value=\"427e12b0-a581-4456-8a3c-4ba895d83f8a\">Euro</option>",
                            "8": ""
                        },
                        "5": {
                            "0": " phx-feedback-for=\"invoice[exchange_rate]\"",
                            "1": {
                                "0": " for=\"invoice-form_exchange_rate\"",
                                "1": {
                                    "0": "Rate"
                                }
                            },
                            "2": " type=\"number\"",
                            "3": " name=\"invoice[exchange_rate]\"",
                            "4": " id=\"invoice-form_exchange_rate\"",
                            "5": " value=\"1\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": " step=\"any\"",
                            "8": ""
                        },
                        "6": {
                            "0": " phx-feedback-for=\"invoice[date]\"",
                            "1": {
                                "0": " for=\"invoice-form_date\"",
                                "1": {
                                    "0": "Date"
                                }
                            },
                            "2": " type=\"date\"",
                            "3": " name=\"invoice[date]\"",
                            "4": " id=\"invoice-form_date\"",
                            "5": " value=\"2023-03-06\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": "",
                            "8": ""
                        },
                        "7": {
                            "0": " phx-feedback-for=\"invoice[delivery_date]\"",
                            "1": {
                                "0": " for=\"invoice-form_delivery_date\"",
                                "1": {
                                    "0": "Delivery Date"
                                }
                            },
                            "2": " type=\"date\"",
                            "3": " name=\"invoice[delivery_date]\"",
                            "4": " id=\"invoice-form_delivery_date\"",
                            "5": " value=\"2023-03-06\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": "",
                            "8": ""
                        },
                        "8": {
                            "0": " phx-feedback-for=\"invoice[payment_term]\"",
                            "1": {
                                "0": " for=\"invoice-form_payment_term\"",
                                "1": {
                                    "0": "Payment Term"
                                }
                            },
                            "2": " type=\"number\"",
                            "3": " name=\"invoice[payment_term]\"",
                            "4": " id=\"invoice-form_payment_term\"",
                            "5": " value=\"30\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": " min=\"0\" step=\"1\"",
                            "8": ""
                        },
                        "9": {
                            "0": " phx-feedback-for=\"invoice[due_date]\"",
                            "1": {
                                "0": " for=\"invoice-form_due_date\"",
                                "1": {
                                    "0": "Due Date"
                                }
                            },
                            "2": " type=\"date\"",
                            "3": " name=\"invoice[due_date]\"",
                            "4": " id=\"invoice-form_due_date\"",
                            "5": " value=\"2023-04-05\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": "",
                            "8": ""
                        },
                        "10": {
                            "0": " phx-feedback-for=\"invoice[payment_method]\"",
                            "1": {
                                "0": " for=\"invoice-form_payment_method\"",
                                "1": {
                                    "0": "Payment Method"
                                }
                            },
                            "2": " id=\"invoice-form_payment_method\"",
                            "3": " name=\"invoice[payment_method]\"",
                            "5": "",
                            "7": "<option selected value=\"transfer\">transfer</option><option value=\"credit\">credit</option><option value=\"card\">card</option><option value=\"cash\">cash</option><option value=\"voucher\">voucher</option>",
                            "8": ""
                        },
                        "11": ""
                    },
                    "1": ""
                }
            }
        }
    }
}

When i change date which doesn’t work:

Parameters: %{"_target" => ["invoice", "date"], "invoice" => %{"bank_account_id" => "bc830162-9b9c-4ba5-a0e7-4a4a8dd414af", "currency_id" => "9425c106-1504-4ae5-a4f0-4dba1f10ae1f", "date" => "2023-03-07", "delivery_date" => "2023-03-06", "due_date" => "2023-04-05", "exchange_rate" => "1", "partner_id" => "2055bf2d-dd76-49be-ab93-b9837e184480", "payment_method" => "transfer", "payment_term" => "30"}}
changeset: #Ecto.Changeset<
  action: :validate,
  changes: %{
    bank_account_id: "bc830162-9b9c-4ba5-a0e7-4a4a8dd414af",
    due_date: ~D[2023-04-05],
    exchange_rate: 1.0
  },
  errors: [],
  data: #Invoicer.Invoice.Invoices.Invoice<>,
  valid?: true

Browser console:

{
    "3": {
        "1": {
            "0": {
                "0": " method=\"post\" errors=\"\" id=\"invoice-form\" phx-change=\"validate\" phx-submit=\"save\"",
                "1": "",
                "2": "",
                "3": {
                    "0": {
                        "2": {
                            "0": " phx-feedback-for=\"invoice[partner_id]\"",
                            "1": {
                                "0": " for=\"invoice-form_partner_id\"",
                                "1": {
                                    "0": "Partner"
                                }
                            },
                            "2": " id=\"invoice-form_partner_id\"",
                            "3": " name=\"invoice[partner_id]\"",
                            "5": "",
                            "7": "<option selected value=\"\"></option><option value=\"2055bf2d-dd76-49be-ab93-b9837e184480\">Székely Kft.</option>",
                            "8": {
                                "d": [
                                    [
                                        {
                                            "0": {
                                                "0": {
                                                    "0": " aria-hidden=\"true\" class=\"mt-0.5 h-5 w-5 flex-none fill-rose-500\" fill=\"currentColor\" viewBox=\"0 0 20 20\"",
                                                    "1": {
                                                        "0": "<path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z\" clip-rule=\"evenodd\"/>",
                                                        "s": 0
                                                    },
                                                    "s": 1
                                                },
                                                "s": 2
                                            },
                                            "1": {
                                                "0": "can&#39;t be blank",
                                                "s": 3
                                            },
                                            "s": 4
                                        }
                                    ]
                                ],
                                "p": {
                                    "0": [
                                        "",
                                        ""
                                    ],
                                    "1": [
                                        "<svg xmlns=\"http://www.w3.org/2000/svg\"",
                                        ">\n  ",
                                        "\n</svg>"
                                    ],
                                    "2": [
                                        "",
                                        ""
                                    ],
                                    "3": [
                                        "",
                                        ""
                                    ],
                                    "4": [
                                        "<p class=\"phx-no-feedback:hidden mt-3 flex gap-3 text-sm leading-6 text-rose-600\">\n  ",
                                        "\n  ",
                                        "\n</p>"
                                    ]
                                },
                                "s": [
                                    "",
                                    ""
                                ]
                            }
                        },
                        "3": "",
                        "4": {
                            "0": " phx-feedback-for=\"invoice[currency_id]\"",
                            "1": {
                                "0": " for=\"invoice-form_currency_id\"",
                                "1": {
                                    "0": "Currency"
                                }
                            },
                            "2": " id=\"invoice-form_currency_id\"",
                            "3": " name=\"invoice[currency_id]\"",
                            "5": "",
                            "7": "<option selected value=\"9425c106-1504-4ae5-a4f0-4dba1f10ae1f\">Forint</option><option value=\"427e12b0-a581-4456-8a3c-4ba895d83f8a\">Euro</option>",
                            "8": ""
                        },
                        "5": {
                            "0": " phx-feedback-for=\"invoice[exchange_rate]\"",
                            "1": {
                                "0": " for=\"invoice-form_exchange_rate\"",
                                "1": {
                                    "0": "Rate"
                                }
                            },
                            "2": " type=\"number\"",
                            "3": " name=\"invoice[exchange_rate]\"",
                            "4": " id=\"invoice-form_exchange_rate\"",
                            "5": " value=\"1.0\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": " step=\"any\"",
                            "8": ""
                        },
                        "6": {
                            "0": " phx-feedback-for=\"invoice[date]\"",
                            "1": {
                                "0": " for=\"invoice-form_date\"",
                                "1": {
                                    "0": "Date"
                                }
                            },
                            "2": " type=\"date\"",
                            "3": " name=\"invoice[date]\"",
                            "4": " id=\"invoice-form_date\"",
                            "5": " value=\"2023-03-07\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": "",
                            "8": ""
                        },
                        "7": {
                            "0": " phx-feedback-for=\"invoice[delivery_date]\"",
                            "1": {
                                "0": " for=\"invoice-form_delivery_date\"",
                                "1": {
                                    "0": "Delivery Date"
                                }
                            },
                            "2": " type=\"date\"",
                            "3": " name=\"invoice[delivery_date]\"",
                            "4": " id=\"invoice-form_delivery_date\"",
                            "5": " value=\"2023-03-06\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": "",
                            "8": ""
                        },
                        "8": {
                            "0": " phx-feedback-for=\"invoice[payment_term]\"",
                            "1": {
                                "0": " for=\"invoice-form_payment_term\"",
                                "1": {
                                    "0": "Payment Term"
                                }
                            },
                            "2": " type=\"number\"",
                            "3": " name=\"invoice[payment_term]\"",
                            "4": " id=\"invoice-form_payment_term\"",
                            "5": " value=\"0\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": " min=\"0\" step=\"1\"",
                            "8": ""
                        },
                        "9": {
                            "0": " phx-feedback-for=\"invoice[due_date]\"",
                            "1": {
                                "0": " for=\"invoice-form_due_date\"",
                                "1": {
                                    "0": "Due Date"
                                }
                            },
                            "2": " type=\"date\"",
                            "3": " name=\"invoice[due_date]\"",
                            "4": " id=\"invoice-form_due_date\"",
                            "5": " value=\"2023-03-06\"",
                            "6": "input-bordered input-primary focus:border-primary input w-full focus:ring-0",
                            "7": "",
                            "8": ""
                        },
                        "10": {
                            "0": " phx-feedback-for=\"invoice[payment_method]\"",
                            "1": {
                                "0": " for=\"invoice-form_payment_method\"",
                                "1": {
                                    "0": "Payment Method"
                                }
                            },
                            "2": " id=\"invoice-form_payment_method\"",
                            "3": " name=\"invoice[payment_method]\"",
                            "5": "",
                            "7": "<option selected value=\"transfer\">transfer</option><option value=\"credit\">credit</option><option value=\"card\">card</option><option value=\"cash\">cash</option><option value=\"voucher\">voucher</option>",
                            "8": ""
                        },
                        "11": ""
                    },
                    "1": ""
                }
            }
        }
    }
}

Which may also be interesting is i don’t assign starter invoice datas in mount, but in apply action, because i use this form inside a liveview, and later i would like to use this liveview as edit mode:

defp apply_action(socket, :new, _params) do
    today = Date.utc_today()
    invoice = %Invoice{
      name: "Draft",
      date: today,
      delivery_date: today,
      due_date: today,
      notes: "",
      state: "new",
      payment_term: 0,
      invoice_lines: []
    }
    changeset =
      invoice
      |> Invoices.change_invoice(%{})
      |> Ecto.Changeset.put_assoc(:invoice_lines, invoice.invoice_lines)

    socket
    |> assign(:page_title, "New Invoice")
    |> assign(:invoice, invoice)
    |> assign(:changeset, changeset)
  end

No. I’ve been watching this on purpose.

Is the invoice_changeset function doing something different in the working partner validate callback compared to the date validate callback? Also, what’s the reasoning behind the put_assoc here? invoice.invoice_lines is coming straight from the socket assigns and not the event data.

Ahh, I think you may be overwriting the due date in your callback function.

The form’s current due_date is being sent back in the parameters nested under "invoice".

This means that the invoice_params from the callback function head includes the "due_date" coming from the form. Assuming Invoices.change_invoice/2 function is pretty standard, the form’s "due_date" in the invoice_params will replace the due_date in the invoice struct that you calculated and defined in the function itself.

Thank you very much for your help and guidance. You helped a lot :slight_smile: It finally helped me to find out where I went wrong. Basically, I should have read the documentation better :smiley: The thing is, I thought that socket.assigns.invoice reflected the state of the form and I started to update it. I rewrote the code and now I update the params fields from the form and compile the changeset that way. So now it works as I had intended.

Like here:

@impl true
  def handle_event("validate", %{"_target" => ["invoice", "date"], "invoice" => invoice_params}, socket) do
    %{"date" => date, "payment_term" => payment_term} = invoice_params
    invoice_params =
      invoice_params
      |> Map.put("due_date", Date.add(Date.from_iso8601!(date), String.to_integer(payment_term)))
      |> Map.put("date", Date.from_iso8601!(date))

    changeset =
      socket.assigns.invoice
      |> Invoices.change_invoice(invoice_params)
      |> Ecto.Changeset.put_assoc(:invoice_lines, socket.assigns.invoice.invoice_lines)
      |> Map.put(:action, :validate)

    socket =
      socket
      |> assign(:changeset, changeset)

    {:noreply, socket}
  end
1 Like

Glad to hear it helped!

Yeah, I did wonder why you were updating a db resource in the socket assigns since that usually only happens after a successful database insert/update/destroy operation. Like you’ve already figured out, it’s the changeset in the socket assigns that’s meant to reflect the state of the form.

Just curious, what’s the intention behind this line? Is this a multi stage form where the associated invoice lines don’t appear until after a valid date is selected?

Nope. The invoice has a has_many relationship with invoice_line schema. I managed the implement a handle_event that add new invoice_lines to the invoice. And i don’t know even if this line should be here, because i thought that every time i update the changeset, i should use put_assoc as well. And this line should also be fixed, because i think here should be invoice_params[“invoice_lines”]. But am I correct that I only need to apply this if the change affects the invoice_line array? :slight_smile:

You can definitely use a changeset without put_assoc when you’re just working with the parent resource. If the form that’s connected to this validation event doesn’t have fields for the associations, which is suggested because that line pulls the invoice_lines off the socket rather than the params, I suspect that the put_assoc line isn’t necessary.

Okay, thank you.