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 }) => ( - - - - ), - ) - .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;