New
Introducing React Bench, see how different models perform on React code

react-doctor/no-async-event-handler-without-reentry-guard

Async mutating handler without re-entry guard

  • Category: Bugs
  • Severity: warn
  • Source: oxlint-plugin-react-doctor
  • Framework: global
  • Enabled when: react
  • Default: Enabled

Validate the diagnostic

Confirm the reported code matches this rule before you edit it.

Confirm the reported code matches react-doctor/no-async-event-handler-without-reentry-guard. Compare it with the Before example and verify the same API, framework, and execution context. Treat it as a false positive only when one of those conditions differs.

Compare the code

The corrected pattern shows a focused way to address the diagnostic.

Reported pattern

function Save({ payload }) {
  const [, setSaved] = useState(false);
  const handleSave = useCallback(async () => {
    await api.post("/save", payload);
    setSaved(true);
  }, [payload]);
  return <button onClick={handleSave} />;
}

Corrected pattern

function Save({ payload }) {
  const [isSaving, setIsSaving] = useState(false);
  const [, setSaved] = useState(false);
  const handleSave = useCallback(async () => {
    if (isSaving) return;
    setIsSaving(true);
    try {
      await api.post("/save", payload);
      setSaved(true);
    } finally {
      setIsSaving(false);
    }
  }, [isSaving, payload]);
  return <button disabled={isSaving} onClick={handleSave} />;
}

How to fix

Use the corrected pattern as a reference. Preserve behavior that the rule does not require you to change.

Copyable fix prompt

Copy this self-contained prompt into your coding agent after you confirm the diagnostic.

Fix every confirmed react-doctor/no-async-event-handler-without-reentry-guard diagnostic in the current repository. Required change: - Block re-entry before the first await. Return when busy is true, or set the flag before entering try. Reset it in finally, and disable the control while the request runs.
Reference transformation:Before:
function Save({ payload }) {
  const [, setSaved] = useState(false);
  const handleSave = useCallback(async () => {
    await api.post("/save", payload);
    setSaved(true);
  }, [payload]);
  return <button onClick={handleSave} />;
}
After:
function Save({ payload }) {
  const [isSaving, setIsSaving] = useState(false);
  const [, setSaved] = useState(false);
  const handleSave = useCallback(async () => {
    if (isSaving) return;
    setIsSaving(true);
    try {
      await api.post("/save", payload);
      setSaved(true);
    } finally {
      setIsSaving(false);
    }
  }, [isSaving, payload]);
  return <button disabled={isSaving} onClick={handleSave} />;
}
Constraints: - Confirm the reported code matches the Before pattern. - Make the smallest change that fixes the root cause. - Preserve behavior and interfaces unrelated to this diagnostic. - Reuse existing project components, utilities, and conventions. - Do not introduce render-phase side effects, render-phase state updates, or Hooks rule violations. - Adapt identifiers and framework details instead of copying blindly. - Do not disable the rule or suppress matching code. - Confirm this rule is enabled for the project: react. Verification: - Run focused tests for the changed behavior. - Run React Doctor and confirm this diagnostic no longer appears. - Report the files changed and any checks you could not run.

More Bugs rules from the rules reference: