A save is rejected with per-field failures. How do you get those messages onto the right inputs without leaving them stale?
answer
- you need a join key
- path, not label or position
- own slot beside client verdicts
- unmapped still surfaces form-level
- edited value has not been judged again
basics
~10 sJoin on a field path both sides agree on, keep server messages in their own slot beside client verdicts, surface unmapped ones at form level, and clear a field's message once its value changes.
solid answer
~50 sMapping needs a **join key**: a stable path that names the offending value in the payload, agreed by both sides — never the label text, never the rendered control's identifier, never position in the form. I keep server-sent messages in a slot separate from client rule verdicts, so re-running client rules cannot erase them and so a server message does not masquerade as a local rule. Every message whose path matches nothing rendered must still surface, as a form-level message, rather than being dropped — that is how a rejected save stops looking like nothing happened. A field's server message is cleared when that value changes, because the server has not seen the new value. Finally I move focus to the first offending field and announce the failure, and I ignore a response belonging to a submission that has been superseded.
go deeper
Know that a rejected save can come back with messages about particular fields, and that those belong next to the inputs they describe rather than in one anonymous banner. The typed values must survive the rejection.
Explain the join key — a payload path both sides agree on — and why label text, generated control identifiers and field order cannot serve as one. Describe where a server message is stored so client rules do not erase it.
Demonstrate the lifecycle: clear a server message when its value changes, surface unmapped complaints at form level, move focus to the first offending field in document order, and ignore a superseded submission's response.
Make the path vocabulary a contract between client and server, with one adapter per endpoint, and measure unmapped-error rate so drift between the payload and the forms shows up as a number rather than a support ticket.
## The join key is a path, not a label A rejected save arrives as a set of complaints. To attach each one to an input, the client needs an identity for the offending value that survives translation, re-ordering and markup changes. That identity is the **path of the value inside the payload you sent** — the same path the form uses to address that field's value and its own rule verdicts. What cannot be the join key: - **Label text.** It is copy; it changes with translation and with a designer's review. - **The rendered control's document identifier.** It is generated, may be scoped or repeated per row, and means nothing to the server. - **Position in the form.** The visual order is a layout decision and diverges from the payload the moment a field is moved or conditionally rendered. So the contract is an agreement on paths. Whatever the API's response shape happens to be, the client's mapping step turns it into a dictionary keyed by payload path, then looks up each rendered field by the same path. Where the two vocabularies genuinely differ — the server names a column, the form names a field — the translation belongs in one adapter per endpoint, not spread through the form. ## Where a server message lives Keep server-sent messages in their **own slot** rather than writing them into the same place client rules write their verdicts. Two reasons: 1. Re-running the client rules — which happens on the next edit or the next submit — would otherwise wipe messages the client has no rule for at all. A server message frequently expresses something a client cannot know: an address that does not exist, a name already taken, a limit reached. 2. Precedence becomes explicit. A field can carry a local verdict *and* a server complaint; you decide which the user sees first instead of discovering it from whichever wrote last. Rendering then reads both slots for a field and shows the highest-precedence message, while the field's invalid state is the union of the two. ## Lifetime A server message describes a specific value at a specific moment. Once that value changes it has not been judged again, so it should be cleared for that field on the next edit — otherwise the user fixes the field, the message stays, and they conclude the form is broken. Two refinements worth stating: - A **cross-field** complaint ("end date must follow start date") should clear when any field it depends on changes, not only the one it was attached to. - Where the client has an equivalent asynchronous rule, the cleared server message can be replaced by that rule's fresh verdict instead of by nothing, so the field does not appear to become valid on the first keystroke. ## Unmapped and form-level failures Some complaints will not match anything rendered: a path for a field this screen does not show, a business rule about the record as a whole, a conflict with another user's write, a rejection with no field information at all. Every such message must still reach the user as a **form-level message**, and one placed where focus can move to it. Silently dropping unmatched complaints is the worst outcome in this whole area: the request failed, nothing changed, and the interface looks exactly as it did before the press. ## Focus, announcement, ordering - Announce the failure through a live region, and say how many fields need attention. - Move focus to the **first offending field in document order**, not in response order — the response's order reflects server-side rule evaluation, not the layout the user reads. - Where several fields failed, a summary that links to each one is worth more than per-field text alone, especially on a long form. - Keep every typed value. A rejection must never cost the user their input. ## Late and superseded responses If a second submission has started, or the user has navigated away from the form, a response from the earlier one must not paint errors over the current state. Tag each submission with an identity and ignore a settlement that does not belong to the current one. The same guard covers the case where a slow rejection lands after a subsequent success. ## What good looks like | Response contains | Client does | |---|---| | A path matching a rendered field | Attach the message to that field's server slot, mark it invalid | | A path matching nothing rendered | Show it as a form-level message, keep the text | | No field information at all | One form-level message, wording distinct from a transport failure | | A complaint about the whole record | Form-level, above the fields, focusable | | A path for a repeated row | Resolve the row by its identity, not its index at render time | The tell of a mature implementation is that a rejected save is never silent, never stale, and never costs the user a keystroke.
- Why not simply show the server's messages as one banner and skip the per-field mapping?Because on a long form the user then has to guess which input the complaint means, and a banner scrolls out of view. Per-field attachment puts the message where the fix happens and lets the invalid state drive both styling and the accessible description. The banner still earns its place as a summary and as the home for complaints that map to no field.
- A complaint names a field this screen does not render. What do you do?Surface it as a form-level message with the server's text, and log it, because it usually means the payload and the form have drifted apart — a field removed from the screen but still sent, or a rule the client does not model. What you must not do is drop it: the write failed and the user has to be told, even if you cannot point at an input.
- How do you stop a slow rejection from an abandoned submit painting errors over a successful one?Give each submission an identity and check it when the response settles: if the settling submission is not the current one, ignore its result entirely — no pending change, no error write. The same check prevents a superseded pending state from clearing the current one, and it is the reason pending and error belong to a submission rather than to the form.
saying these in an interview costs you the question
- Matching server messages to fields by label text
- Writing server messages into the client rule slot
- Dropping complaints that match no rendered field
- Leaving a server message after the field was edited
- Clearing the form's values when the save is rejected
- Focusing the response's first field, not the document's