It is tempting to treat this as enough protection against duplicate submissions:
The button is worth disabling. It gives the user immediate feedback and prevents an obvious second click. But it only controls one interaction in one browser tab.
It cannot tell us whether a request was retried by the client, whether two requests crossed the network at nearly the same time, or whether the server completed a write before the response disappeared.
Now imagine that the server creates the order, but the response never reaches the browser. The UI sees a network error. The user tries again. Without any other protection, the second request can create a second order.
The difficult part is not the second click. The difficult part is deciding whether the second request represents a new operation or a retry of the first one.
The mental model I use separates two identities: A fingerprint identifies the content of the request. An idempotency key identifies the operation the user intended to perform.
Two form values can be different in JavaScript while representing the same business input.
But if the application trims names before saving them, that whitespace is not a meaningful change. The same issue appears with values such as "150" and 150, or 150 and 150.0.
Converting the price to cents is part of the example's business normalization. It avoids using small floating-point differences as evidence that two prices are different.
For storage or comparison across system boundaries, I would usually hash that canonical value:
The normalization rules must come from the domain. If array order has no business meaning, sort the array before hashing it. If order is meaningful, sorting it would create the wrong identity. A fingerprint is only as correct as the canonical representation behind it.
Form libraries usually expose an isDirty flag. That flag is useful, but it normally compares raw form values with the original defaults. It does not know which differences matter to the business.
A business-aware check can compare the initial and current fingerprints instead:
If the user changes "Sara" to " Sara ", the raw form may be dirty while isMeaningfullyDirty remains false.
If the user changes amount from 2 to 3, the fingerprint changes. If they change it back to 2, the fingerprints match again and the form is no longer meaningfully dirty.
This is also why UI-only state should stay out of the fingerprint. A search query, an expanded panel, or the selected tab may change the component state without changing the order that will be sent to the server.
Using the same canonicalization rules for Dirty State and submission gives both features the same definition of a meaningful change.
A fingerprint tells us that two payloads contain the same business data. It does not tell us whether the user intended one operation or two.
