diff --git a/CHANGELOG.md b/CHANGELOG.md
index 260f0f777..bad10ebd7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
### 🪄 Improvements
- Add `react/no-clone-element` to `recommended` and `recommended-legacy` presets.
+- Improve rule `react/no-unstable-nested-components`, make its behavior closer to [react-hooks/no-nested-components](https://github.com/facebook/react/pull/25360).
## v0.10.0 (Thu Dec 21 2023)
diff --git a/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.spec.ts b/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.spec.ts
index 21b10891d..69206e453 100644
--- a/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.spec.ts
+++ b/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.spec.ts
@@ -47,63 +47,6 @@ ruleTester.run(RULE_NAME, rule, {
});
}
`,
- dedent`
- function ParentComponent() {
- const MemoizedNestedComponent = React.useCallback(() =>
, []);
-
- return (
-
-
-
- );
- }
- `,
- dedent`
- function ParentComponent() {
- const MemoizedNestedComponent = React.useCallback(
- () => React.createElement("div", null),
- []
- );
-
- return React.createElement(
- "div",
- null,
- React.createElement(MemoizedNestedComponent, null)
- );
- }
- `,
- dedent`
- function ParentComponent() {
- const MemoizedNestedFunctionComponent = React.useCallback(
- function () {
- return ;
- },
- []
- );
-
- return (
-
-
-
- );
- }
- `,
- dedent`
- function ParentComponent() {
- const MemoizedNestedFunctionComponent = React.useCallback(
- function () {
- return React.createElement("div", null);
- },
- []
- );
-
- return React.createElement(
- "div",
- null,
- React.createElement(MemoizedNestedFunctionComponent, null)
- );
- }
- `,
dedent`
function ParentComponent(props) {
// Should not interfere handler declarations
@@ -402,69 +345,6 @@ ruleTester.run(RULE_NAME, rule, {
}
}
`,
- dedent`
- function App({ locale }: AppProps) {
- const route = Router.useRoute(["Home", "BotArea", "NotFound"]);
-
- return (
-
-
-
- } />}>
- {React.useMemo(
- () => match(route)
- .with({ name: "Home" }, () => )
- .with({ name: "BotArea" }, ({ params }) => )
- .otherwise(() => ),
- [loaded, route],
- )}
-
-
-
-
- );
- }
- `,
- dedent`
- function BotArea({ botName }: BotAreaProps) {
- const bot = useAtomValue(botsDb.item(botName));
- const route = Router.useRoute(["BotRoot", "BotChat", "BotNewChat", "BotSettings"]);
- const botList = useAtomValue(botListAtom);
-
- const contentView = React.useMemo(
- () =>
- match(route)
- .with({ name: "BotRoot" }, ({ params }) => )
- .with({ name: "BotNewChat" }, ({ params }) => )
- .with({ name: "BotSettings" }, ({ params }) => )
- .with({ name: "BotChat" }, ({ params }) => {
- const { botName, chatID } = params;
-
- if (!ID.isChatID(chatID)) {
- return ;
- }
-
- return ;
- })
- .otherwise(() => null),
- [route],
- );
-
- if (!bot) {
- return ;
- }
-
- return (
-
- }>
- Failed to render bot area.}>
- {contentView}
-
-
-
- );
- }
- `,
dedent`
function ComponentWithProps(props) {
return ;
@@ -1007,45 +887,72 @@ ruleTester.run(RULE_NAME, rule, {
},
{
code: dedent`
- export function BotList({ items, selected }: BotListProps) {
- return (
-
- {items.map((item) => {
- return match(item)
- .when(
- ({ id }) => id === selected,
- ({ id, title, icon }) => (
-
- }
- clickOnEnter
- clickOnSpace
- >
-
-
-
- ),
- )
- .otherwise(({ id, title, icon }) => (
-
-
-
- ));
- })}
-
-
-
-
-
-
- );
+ function ParentComponent() {
+ const MemoizedNestedComponent = React.useCallback(() => , []);
+
+ return (
+
+
+
+ );
}
`,
- errors: [
- { messageId: "UNSTABLE_NESTED_COMPONENT" },
- { messageId: "UNSTABLE_NESTED_COMPONENT" },
- ],
+ errors: [{ messageId: "UNSTABLE_NESTED_COMPONENT" }],
+ },
+ {
+ code: dedent`
+ function ParentComponent() {
+ const MemoizedNestedComponent = React.useCallback(
+ () => React.createElement("div", null),
+ []
+ );
+
+ return React.createElement(
+ "div",
+ null,
+ React.createElement(MemoizedNestedComponent, null)
+ );
+ }
+ `,
+ errors: [{ messageId: "UNSTABLE_NESTED_COMPONENT" }],
+ },
+ {
+ code: dedent`
+ function ParentComponent() {
+ const MemoizedNestedFunctionComponent = React.useCallback(
+ function () {
+ return ;
+ },
+ []
+ );
+
+ return (
+
+
+
+ );
+ }
+ `,
+ errors: [{ messageId: "UNSTABLE_NESTED_COMPONENT" }],
+ },
+ {
+ code: dedent`
+ function ParentComponent() {
+ const MemoizedNestedFunctionComponent = React.useCallback(
+ function () {
+ return React.createElement("div", null);
+ },
+ []
+ );
+
+ return React.createElement(
+ "div",
+ null,
+ React.createElement(MemoizedNestedFunctionComponent, null)
+ );
+ }
+ `,
+ errors: [{ messageId: "UNSTABLE_NESTED_COMPONENT" }],
},
],
});
diff --git a/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.ts b/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.ts
index c81d7b52c..bc1e631da 100644
--- a/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.ts
+++ b/packages/plugins/eslint-plugin-react/src/rules/no-unstable-nested-components.ts
@@ -9,7 +9,6 @@ import {
} from "@eslint-react/ast";
import {
ERComponentCollectorHint,
- isInsideReactHookCall,
isInsideRenderMethod,
unsafeIsDeclaredInRenderProp,
unsafeIsDirectValueOfRenderProperty,
@@ -73,12 +72,8 @@ export default createRule<[], MessageID>({
return isClass(node) && classComponents.some(component => component.node === node);
};
for (const { node: component } of functionComponents) {
- if (
- // Do not mark components declared inside hooks (or falsy '() => null' clean-up methods)
- isInsideReactHookCall(component)
- // Do not mark objects containing render methods
- || unsafeIsDirectValueOfRenderProperty(component)
- ) {
+ // Do not mark objects containing render methods
+ if (unsafeIsDirectValueOfRenderProperty(component)) {
continue;
}
const isInsideProperty = component.parent.type === NodeType.Property;