Rule: react-doctor/no-loading-flag-reset-outside-finally
Summary
The rule flags a setLoading(false) placed after a try/catch block as "reset only on the success path." But when the catch clause handles the error without re-throwing, execution falls through to the trailing reset on both the success and the caught-rejection paths. The flag is never stuck truthy, so there is no bug — this is functionally equivalent to a finally reset.
The rule's detector appears to only check whether the reset is lexically inside finally, without tracing that the trailing statement is reachable from a non-rethrowing catch.
Actual Code
const handleUpload = async () => {
if (!selectedFile || isUploading) return;
setIsUploading(true);
try {
const mimeType = selectedFile.mimeType ?? "application/octet-stream";
const formData = new RawNameFormData();
const filePart = {
uri: selectedFile.uri,
name: selectedFile.name,
type: mimeType,
};
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
formData.append("file1", filePart as unknown as Blob);
const res = await authedFetch(`${API_BASE}/lectures/${lectureId}/tasks/${taskId}/submit`, {
method: "POST",
body: formData,
});
if (res.ok) {
onSuccess();
setSelectedFile(null);
} else {
toast.show({ variant: "danger", label: t("lectures.grades.uploadError"), duration: 3500 });
}
} catch {
toast.show({ variant: "danger", label: t("lectures.grades.uploadError"), duration: 3500 });
}
setIsUploading(false);
};
Actual: no-loading-flag-reset-outside-finally fires on the trailing setIsUploading(false).
Expected: no diagnostic — the reset is guaranteed to run on rejection because the catch does not re-throw.
Why this matters — conflict with react-hooks-js/todo
The suggested fix (move the reset into finally) is not viable in a React Compiler codebase. react-hooks-js/todo reports:
Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
React Compiler cannot currently lower try/finally (or try without catch). So the two rules are in direct conflict:
no-loading-flag-reset-outside-finally → wants finally
react-hooks-js/todo → forbids finally
The catch-without-rethrow + trailing-reset pattern is the only shape that satisfies both, yet this rule flags it.
Environment
- "react-doctor": "0.9.5"
- React Compiler /
react-hooks-js enabled: yes
- React 19, React Native 0.85 (Expo 56) (React Compiler enabled)
Rule:
react-doctor/no-loading-flag-reset-outside-finallySummary
The rule flags a
setLoading(false)placed after atry/catchblock as "reset only on the success path." But when thecatchclause handles the error without re-throwing, execution falls through to the trailing reset on both the success and the caught-rejection paths. The flag is never stuck truthy, so there is no bug — this is functionally equivalent to afinallyreset.The rule's detector appears to only check whether the reset is lexically inside
finally, without tracing that the trailing statement is reachable from a non-rethrowingcatch.Actual Code
Actual:
no-loading-flag-reset-outside-finallyfires on the trailingsetIsUploading(false).Expected: no diagnostic — the reset is guaranteed to run on rejection because the
catchdoes not re-throw.Why this matters — conflict with
react-hooks-js/todoThe suggested fix (move the reset into
finally) is not viable in a React Compiler codebase.react-hooks-js/todoreports:React Compiler cannot currently lower
try/finally(ortrywithoutcatch). So the two rules are in direct conflict:no-loading-flag-reset-outside-finally→ wantsfinallyreact-hooks-js/todo→ forbidsfinallyThe
catch-without-rethrow + trailing-reset pattern is the only shape that satisfies both, yet this rule flags it.Environment
react-hooks-jsenabled: yes