When UI actions can occur rapidly or in parallel (double-clicks, retries, rapid test submissions), treat them as concurrency problems: synchronize the triggering and centralize the side effects.
Apply two rules: 1) Guard “repeatable” actions (submit/reset/etc.) with stable synchronization and cleanup.
const SUBMIT_DEBOUNCE_MS = 1000;
export function useSubmit() {
const dispatch = useDispatch();
const submitRef = useRef(() => dispatch(submitChallenge()));
submitRef.current = () => dispatch(submitChallenge());
const debouncedSubmitRef = useRef(
debounce(() => submitRef.current(), SUBMIT_DEBOUNCE_MS, {
leading: true,
trailing: false,
})
);
useEffect(() => {
const debouncedSubmit = debouncedSubmitRef.current;
return () => debouncedSubmit.cancel();
}, []);
return () => debouncedSubmitRef.current();
}
2) Centralize API + store mutation in a single orchestrator (e.g., saga).
Result: fewer race-condition bugs, more deterministic behavior under fast user input and automated rapid submissions, and clearer ownership of async workflows.
Enter the URL of a public GitHub repository