Input field is updated but hook callback is never called

Hello,

I noticed that in some cases the input value will change/be updated after a phx-change call from a form but its hook code will never be called.

For example, consider this hook:

import Imask from "imask"

const PriceUSDMaskHook = {
  mask: null,
  mounted() {
    this.mask = IMask(this.el, {
      mask: Number,
      scale: 2,
      signed: false,
      thousandsSeparator: ",",
      radix: ".",
    })
  },
  beforeUpdate() {
    console.log(">>>>> beforeUpdate", this.el.value)

  },
  updated() {
    console.log(">>>>> updated begin", this.el.value)
    this.mask.value = this.el.value
    console.log(">>>>> updated end", this.el.value)
  },
  destroyed() {},
  disconnected() {},
  reconnected() {}
}

export default PriceUSDMaskHook

Now, let’s say I have this form:

<.form :let={f} for={@blibs} phx-change="validate">
  <label>element 1</label>
   <.text_input form={f} field={:price} phx-hook="PriceUSDMaskHook" />

  <label>element 2</label>
  <.text_input form={f} field={:blobs} />
</.form>

And I have this validation code:

def handle_event("validate", params, socket) do
    params = maybe_process_price(params)

    {:noreply, assign(socket, blibs: params)}
  end

  defp maybe_process_price(%{"price" => ""} = params), do: params

  defp maybe_process_price(%{"price" => price} = params) when is_binary(price) do
    price = price |> Money.parse!(:USD) |> Money.to_string(symbol: false, separator: "")

    %{params | "price" => price}
  end

As you can see, my hook will reformat the number the user typed into a nicer format, for example, 23423 will be changed to 23,423.

When I send that value to the validate function, I need to convert it back to a decimal since my schema accepts a price as decimal, I removed the schema part and the actual validation since that is not relevant here, but in the real code I also run that function to convert the price string into a decimal and I replace that value in the form before doing validation.

As a side-effect of that, the returned form will container the decimal, not the original string, that’s why I have the updated callback implemented in my hook since that callback will reapply the mask so the final number is, again, 23,423.

This works fine if I’m changing that input element, but it fails if I change any other input element inside the form (like the element 2 in this example).

What will happen in this case is that the new form returned will have that value changed, but LiveView will not detect that and will never call the updated callback, so the value 23,423 will be replaced with 23423.

I believe this is a bug, but I wanted to check here in the forum first before creating a ticket in github.