Skip to content

Commit efca2d0

Browse files
committed
Add RulesOfHooks support for use
Usage of the new `use` hook needs to conform to the rules of hooks, with the one exception that it can be called conditionally. ghstack-source-id: 35b7e1c Pull Request resolved: #25370
1 parent d71c084 commit efca2d0

File tree

2 files changed

+99
-4
lines changed

2 files changed

+99
-4
lines changed

packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ const tests = {
257257
code: normalizeIndent`
258258
// Valid because they're not matching use[A-Z].
259259
fooState();
260-
use();
261260
_use();
262261
_useState();
263262
use_hook();
@@ -496,8 +495,6 @@ const tests = {
496495
},
497496
{
498497
code: normalizeIndent`
499-
Hook.use();
500-
Hook._use();
501498
Hook.useState();
502499
Hook._useState();
503500
Hook.use42();
@@ -1146,6 +1143,25 @@ if (__EXPERIMENTAL__) {
11461143
}
11471144
`,
11481145
},
1146+
{
1147+
code: normalizeIndent`
1148+
function App() {
1149+
const text = use(Promise.resolve('A'));
1150+
return <Text text={text} />
1151+
}
1152+
`,
1153+
},
1154+
{
1155+
code: normalizeIndent`
1156+
function App() {
1157+
if (shouldShowText) {
1158+
const text = use(query);
1159+
return <Text text={text} />
1160+
}
1161+
return <Text text={shouldFetchBackupText ? use(backupQuery) : "Nothing to see here"} />
1162+
}
1163+
`,
1164+
},
11491165
];
11501166
tests.invalid = [
11511167
...tests.invalid,
@@ -1220,6 +1236,66 @@ if (__EXPERIMENTAL__) {
12201236
`,
12211237
errors: [useEventError('onClick')],
12221238
},
1239+
{
1240+
code: normalizeIndent`
1241+
Hook.use();
1242+
Hook._use();
1243+
Hook.useState();
1244+
Hook._useState();
1245+
Hook.use42();
1246+
Hook.useHook();
1247+
Hook.use_hook();
1248+
`,
1249+
errors: [
1250+
topLevelError('Hook.use'),
1251+
topLevelError('Hook.useState'),
1252+
topLevelError('Hook.use42'),
1253+
topLevelError('Hook.useHook'),
1254+
],
1255+
},
1256+
{
1257+
code: normalizeIndent`
1258+
function notAComponent() {
1259+
use(promise);
1260+
}
1261+
`,
1262+
errors: [functionError('use', 'notAComponent')],
1263+
},
1264+
{
1265+
code: normalizeIndent`
1266+
function App() {
1267+
const data = useUserQuery((searchStrings) => {
1268+
const query = Promise.all(fetch(user, searchStrings));
1269+
return use(query);
1270+
});
1271+
return (
1272+
<ul>
1273+
{data.map(d => <li>{d.title}</li>)}
1274+
</ul>
1275+
);
1276+
}
1277+
`,
1278+
errors: [genericError('use')],
1279+
},
1280+
{
1281+
code: normalizeIndent`
1282+
const text = use(promise);
1283+
function App() {
1284+
return <Text text={text} />
1285+
}
1286+
`,
1287+
errors: [topLevelError('use')],
1288+
},
1289+
{
1290+
code: normalizeIndent`
1291+
class C {
1292+
m() {
1293+
use(promise);
1294+
}
1295+
}
1296+
`,
1297+
errors: [classError('use')],
1298+
},
12231299
];
12241300
}
12251301

packages/eslint-plugin-react-hooks/src/RulesOfHooks.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
*/
1717

1818
function isHookName(s) {
19+
if (__EXPERIMENTAL__) {
20+
return s === 'use' || /^use[A-Z0-9]/.test(s);
21+
}
1922
return /^use[A-Z0-9]/.test(s);
2023
}
2124

@@ -107,6 +110,13 @@ function isUseEventIdentifier(node) {
107110
return false;
108111
}
109112

113+
function isUseIdentifier(node) {
114+
if (__EXPERIMENTAL__) {
115+
return node.type === 'Identifier' && node.name === 'use';
116+
}
117+
return false;
118+
}
119+
110120
export default {
111121
meta: {
112122
type: 'problem',
@@ -480,6 +490,13 @@ export default {
480490
//
481491
// Special case when we think there might be an early return.
482492
if (!cycled && pathsFromStartToEnd !== allPathsFromStartToEnd) {
493+
let shouldReport = true;
494+
if (__EXPERIMENTAL__) {
495+
// `use(...)` can be called conditionally.
496+
if (isUseIdentifier(hook)) {
497+
shouldReport = false;
498+
}
499+
}
483500
const message =
484501
`React Hook "${context.getSource(hook)}" is called ` +
485502
'conditionally. React Hooks must be called in the exact ' +
@@ -488,7 +505,9 @@ export default {
488505
? ' Did you accidentally call a React Hook after an' +
489506
' early return?'
490507
: '');
491-
context.report({node: hook, message});
508+
if (shouldReport) {
509+
context.report({node: hook, message});
510+
}
492511
}
493512
} else if (
494513
codePathNode.parent &&

0 commit comments

Comments
 (0)