Interview question
Prevent a duplicate form mutation
Guards a mutation immediately, exposes pending state, and restores the command after failure.
TL;DR
Guards a mutation immediately, exposes pending state, and restores the command after failure.
Synchronous guards, pending UI, retries, idempotency keys, and accessible feedback.
Practice the problem like a real interview: restate, reason, implement, and test.
The save command may take several seconds. Prevent duplicate calls before React renders the disabled state, while allowing retry after failure.
Next in Forms & Accessibility: Upload Recovery
Two rapid submit events produce one fetch call and one stable idempotency key.
A ref closes the gap before the pending render, while state communicates progress. I also keep one attempt object containing the normalized command and idempotency key. A definitive rejection clears that attempt so corrected input can start a new operation; an ambiguous transport failure retains it, so Retry sends the same payload with the same key instead of risking a duplicate order.
type Attempt = {
key: string;
command: CreateOrderInput;
};
const submittingRef = useRef(false);
const attemptRef = useRef<Attempt | null>(null);
const [pending, setPending] = useState(false);
const [error, setError] = useState<string | null>(null);
async function submit(event: FormEvent) {
event.preventDefault();
if (submittingRef.current) return;
submittingRef.current = true;
setPending(true);
setError(null);
const attempt = attemptRef.current ?? {
key: crypto.randomUUID(),
command: normalizeOrder(values),
};
attemptRef.current = attempt;
try {
await api.createOrder(attempt.command, {
idempotencyKey: attempt.key,
});
attemptRef.current = null;
} catch (caught) {
if (isDefinitiveRejection(caught)) {
attemptRef.current = null;
setError("Review the order details and try again.");
} else {
setError("The result is uncertain. Retry to check the same order.");
}
} finally {
submittingRef.current = false;
setPending(false);
}
}
return <>
<button type="submit" disabled={pending} aria-busy={pending}>
{pending ? "Creating order" : "Create order"}
</button>
{error ? <p role="alert">{error}</p> : null}
</>;
isDefinitiveRejection should be based on the API client's typed outcome, not on guessing from a generic exception. While an outcome is uncertain, Retry reuses the captured command and key; changing it should be an explicit new operation after reconciliation.
The client guard improves UX; database-backed idempotency provides correctness if the browser retries after an uncertain response.
Share the question with a concise Core-answer excerpt and invite other developers to add their perspective.
Help improve the interview library.
Say thanks with a standalone, one-time $5 contribution. No account required.
Aporeon is shaped by Aleksandar Tomovski, a software developer with experience on both sides of technical interviews. Content is reviewed for accuracy, natural spoken delivery, useful depth, and honest trade-offs.