react-doctor/loading-action-preserves-trigger
Loading state removes its initiating action
- Category: Accessibility
- Severity: warn
- Source:
oxlint-plugin-react-doctor - Framework: global
- Enabled when: react
- Tags: react-jsx-only
- Default: Disabled until configured
Validate the diagnostic
Confirm the reported code matches this rule before you edit it.
Confirm the reported code matches react-doctor/loading-action-preserves-trigger. 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
import { useState } from "react";
const SaveButton = () => {
const [isSaving, setIsSaving] = useState(false);
const save = async () => {
setIsSaving(true);
await fetch("/api/save", { method: "POST" });
setIsSaving(false);
};
return isSaving
? <span role="status">Saving</span>
: <button onClick={save}>Save</button>;
};Corrected pattern
import { useState } from "react";
const SaveButton = () => {
const [isSaving, setIsSaving] = useState(false);
const save = async () => {
setIsSaving(true);
await fetch("/api/save", { method: "POST" });
setIsSaving(false);
};
return (
<button disabled={isSaving} onClick={save}>
<span className={isSaving ? "invisible" : ""}>Save</span>
{isSaving && <span role="status">Saving…</span>}
</button>
);
};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
Reference transformation:Before:react-doctor/loading-action-preserves-trigger diagnostic in the current repository.
Required change:
- Keep the initiating control mounted while work is pending. Disable it or mark it busy, and put the spinner or status inside the same control so focus and action identity remain stable.import { useState } from "react";
const SaveButton = () => {
const [isSaving, setIsSaving] = useState(false);
const save = async () => {
setIsSaving(true);
await fetch("/api/save", { method: "POST" });
setIsSaving(false);
};
return isSaving
? <span role="status">Saving</span>
: <button onClick={save}>Save</button>;
};import { useState } from "react";
const SaveButton = () => {
const [isSaving, setIsSaving] = useState(false);
const save = async () => {
setIsSaving(true);
await fetch("/api/save", { method: "POST" });
setIsSaving(false);
};
return (
<button disabled={isSaving} onClick={save}>
<span className={isSaving ? "invisible" : ""}>Save</span>
{isSaving && <span role="status">Saving…</span>}
</button>
);
};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.
- Preserve accessible names, focus order, keyboard behavior, and touch access not targeted by this rule.
- 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.Related rules
More Accessibility rules from the rules reference:
react-doctor/media-has-caption: Add a `<track kind="captions">` child to every `<audio>` / `<video>`.react-doctor/mouse-events-have-key-events: Pair mouse events with their keyboard equivalents.react-doctor/no-access-key: Don't use `accessKey` — it conflicts with assistive-technology shortcuts.react-doctor/no-all-caps-body-text: Long body copy is set in all capsreact-doctor/no-arbitrary-px-font-size: Pixel arbitrary font size