Repository: jsx-eslint/eslint-plugin-react
Pull request #3490 (2022-11-15 12:34:06)

[Fix] `jsx-key`: detect keys in logical expression and conditional expression
Fixes https://github.com/jsx-eslint/eslint-plugin-react/issues/3481
I've also updated the docs to stay consistent with the comment from below `In the last example the key is being spread...`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 291ed244..ed11d157 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,12 +16,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 * [`jsx-no-target-blank`]: allow ternaries with literals ([#3464][] @akulsr0)
 * [`sort-prop-types`]: restore autofixing ([#2574][] @ROSSROSALES)
 * [`no-unknown-property`]: add `inert` attribute ([#3484][] @ljharb)
+* [`jsx-key`]: detect keys in logical expression and conditional expression ([#3490][] @metreniuk)
 
 ### Changed
 * [Perf] component detection: improve performance by avoiding traversing parents unnecessarily ([#3459][] @golopot)
 * [Docs] `forbid-component-props`: inclusive language w/ allowlist ([#3473][] @AndersDJohnson)
 * [Docs] automate doc generation with `eslint-doc-generator` ([#3469][] @bmish)
 
+[#3490]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3490
 [#3484]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3484
 [#3473]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3473
 [#3469]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3469
diff --git a/docs/rules/jsx-key.md b/docs/rules/jsx-key.md
index 1076d4c1..0415b6df 100644
--- a/docs/rules/jsx-key.md
+++ b/docs/rules/jsx-key.md
@@ -20,11 +20,11 @@ data.map(x => <Hello>{x}</Hello>);
 ```
 
 ```jsx
-<Hello {...{ key: id, id, caption }} />
+Array.from([1, 2, 3], (x) => <Hello>{x}</Hello>);
 ```
 
 ```jsx
-Array.from([1, 2, 3], (x) => <Hello>{x}</Hello>);
+<Hello {...{ key: id, id, caption }} />
 ```
 
 In the last example the key is being spread, which is currently possible, but discouraged in favor of the statically provided key.
@@ -40,11 +40,11 @@ data.map((x) => <Hello key={x.id}>{x}</Hello>);
 ```
 
 ```jsx
-<Hello key={id} {...{ id, caption }} />
+Array.from([1, 2, 3], (x) => <Hello key={x.id}>{x}</Hello>);
 ```
 
 ```jsx
-Array.from([1, 2, 3], (x) => <Hello key={x.id}>{x}</Hello>);
+<Hello key={id} {...{ id, caption }} />
 ```
 
 ## Rule Options
diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js
index b1e0a62a..263ed824 100644
--- a/lib/rules/jsx-key.js
+++ b/lib/rules/jsx-key.js
@@ -149,10 +149,20 @@ module.exports = {
      */
     function checkArrowFunctionWithJSX(node) {
       const isArrFn = node && node.type === 'ArrowFunctionExpression';
-
-      if (isArrFn && (node.body.type === 'JSXElement' || node.body.type === 'JSXFragment')) {
+      const shouldCheckNode = (n) => n && (n.type === 'JSXElement' || n.type === 'JSXFragment');
+      if (isArrFn && shouldCheckNode(node.body)) {
         checkIteratorElement(node.body);
       }
+      if (node.body.type === 'ConditionalExpression') {
+        if (shouldCheckNode(node.body.consequent)) {
+          checkIteratorElement(node.body.consequent);
+        }
+        if (shouldCheckNode(node.body.alternate)) {
+          checkIteratorElement(node.body.alternate);
+        }
+      } else if (node.body.type === 'LogicalExpression' && shouldCheckNode(node.body.right)) {
+        checkIteratorElement(node.body.right);
+      }
     }
 
     const childrenToArraySelector = `:matches(


Test patch:
diff --git a/tests/lib/rules/jsx-key.js b/tests/lib/rules/jsx-key.js
index 15b89c19..393affca 100644
--- a/tests/lib/rules/jsx-key.js
+++ b/tests/lib/rules/jsx-key.js
@@ -42,6 +42,8 @@ ruleTester.run('jsx-key', rule, {
     { code: '[<App key={0} />, <App key={1} />];' },
     { code: '[1, 2, 3].map(function(x) { return <App key={x} /> });' },
     { code: '[1, 2, 3].map(x => <App key={x} />);' },
+    { code: '[1, 2 ,3].map(x => x && <App x={x} key={x} />);' },
+    { code: '[1, 2 ,3].map(x => x ? <App x={x} key="1" /> : <OtherApp x={x} key="2" />);' },
     { code: '[1, 2, 3].map(x => { return <App key={x} /> });' },
     { code: 'Array.from([1, 2, 3], function(x) { return <App key={x} /> });' },
     { code: 'Array.from([1, 2, 3], (x => <App key={x} />));' },
@@ -188,10 +190,10 @@ ruleTester.run('jsx-key', rule, {
       code: `
         import Act from 'react';
         import { Children as ReactChildren } from 'react';
-        
+
         const { Children } = Act;
         const { toArray } = Children;
-        
+
         Act.Children.toArray([1, 2 ,3].map(x => <App />));
         Act.Children.toArray(Array.from([1, 2 ,3], x => <App />));
         Children.toArray([1, 2 ,3].map(x => <App />));
@@ -225,6 +227,18 @@ ruleTester.run('jsx-key', rule, {
       code: '[1, 2 ,3].map(x => <App />);',
       errors: [{ messageId: 'missingIterKey' }],
     },
+    {
+      code: '[1, 2 ,3].map(x => x && <App x={x} />);',
+      errors: [{ messageId: 'missingIterKey' }],
+    },
+    {
+      code: '[1, 2 ,3].map(x => x ? <App x={x} key="1" /> : <OtherApp x={x} />);',
+      errors: [{ messageId: 'missingIterKey' }],
+    },
+    {
+      code: '[1, 2 ,3].map(x => x ? <App x={x} /> : <OtherApp x={x} key="2" />);',
+      errors: [{ messageId: 'missingIterKey' }],
+    },
     {
       code: '[1, 2 ,3].map(x => { return <App /> });',
       errors: [{ messageId: 'missingIterKey' }],