Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/fix-950-incompatible-library-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@react-doctor/core": patch
---

Fix misleading remediation for react-hooks-js/incompatible-library

`react-hooks-js/incompatible-library` fires when the React Compiler can't
memoize through a third-party hook (e.g. `@tanstack/react-virtual`'s
`useVirtualizer`). The diagnostic carried the generic React Compiler action —
"Rewrite the flagged code so the compiler can optimize it" — which reads as
"reimplement the library locally" and steered users off mature libraries.

The rule stays active (the compiler's own bail-out reason is informative), but
its remediation now names the real fix: it's how the library works, not a bug in
your code — memoize values you pass from it into other memoized components, or
suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.

Closes #950
22 changes: 18 additions & 4 deletions packages/core/src/runners/oxlint/parse-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,21 @@ const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
const REACT_COMPILER_IMPACT =
"This component misses React Compiler's automatic memoization & re-renders more than it should";
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
// `incompatible-library` fires on a third-party hook the compiler can't memoize
// through (e.g. @tanstack/react-virtual's `useVirtualizer`) — code the user
// can't and shouldn't rewrite. The generic "rewrite it" action wrongly steers
// users off mature libraries (#950), so this rule names the real fix instead.
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION =
"It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;

const buildReactCompilerMessage = (reasonSummary: string): string => {
const buildReactCompilerMessage = (
reasonSummary: string,
action = REACT_COMPILER_ACTION,
): string => {
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
if (!normalizedSummary) return REACT_COMPILER_GENERIC_MESSAGE;
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${REACT_COMPILER_ACTION}`;
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
};

// Adopted third-party plugins (not in the react-doctor registry) → the
Expand Down Expand Up @@ -167,7 +176,12 @@ const resolveCleanedDiagnostic = (
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
const reasonDetail = reasonDetailLines.join("\n").trim();
return {
message: buildReactCompilerMessage(reasonSummary.trim()),
message: buildReactCompilerMessage(
reasonSummary.trim(),
rule === "incompatible-library"
? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION
: REACT_COMPILER_ACTION,
),
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project),
};
}
Expand Down
14 changes: 14 additions & 0 deletions packages/core/tests/react-compiler-bailout-message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,18 @@ describe("parseOxlintOutput react-hooks-js bail-out reason in primary message",

expect(diagnostic.message).toContain(": This value is impure. Rewrite");
});

it("gives incompatible-library a library-specific action, not the generic 'rewrite it' copy", () => {
const reason =
"This API returns functions which cannot be memoized without leading to stale UI";
const stdout = buildOxlintStdout("react-hooks-js(incompatible-library)", reason);
const [diagnostic] = parseOxlintOutput(stdout, buildProject(), TEST_ROOT_DIRECTORY);

expect(diagnostic.message).toContain(reason);
expect(diagnostic.message).toContain("not a bug in your code");
expect(diagnostic.message).toContain(
"react-doctor-disable-next-line react-hooks-js/incompatible-library",
);
expect(diagnostic.message).not.toContain("Rewrite the flagged code");
});
});
Loading