Skip to content

Commit

Permalink
Add RulesOfHooks support for use
Browse files Browse the repository at this point in the history
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: 7ea5beceaf2c080f2c48821b5117bdd0c1194836
Pull Request resolved: #25370
  • Loading branch information
poteto committed Oct 4, 2022
1 parent 338e6a9 commit 3fd9bd8
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ const tests = {
code: normalizeIndent`
// Valid because they're not matching use[A-Z].
fooState();
use();
_use();
_useState();
use_hook();
Expand Down Expand Up @@ -496,8 +495,6 @@ const tests = {
},
{
code: normalizeIndent`
Hook.use();
Hook._use();
Hook.useState();
Hook._useState();
Hook.use42();
Expand Down Expand Up @@ -1146,6 +1143,45 @@ if (__EXPERIMENTAL__) {
}
`,
},
{
code: normalizeIndent`
function App() {
const text = use(Promise.resolve('A'));
return <Text text={text} />
}
`,
},
{
code: normalizeIndent`
function App() {
if (shouldShowText) {
const text = use(query);
return <Text text={text} />
}
return <Text text={shouldFetchBackupText ? use(backupQuery) : "Nothing to see here"} />
}
`,
},
{
code: normalizeIndent`
function App() {
let data = [];
for (const query of queries) {
const text = use(item);
data.push(text);
}
return <Child data={data} />
}
`,
},
{
code: normalizeIndent`
function App() {
const data = someCallback((x) => use(x));
return <Child data={data} />
}
`,
},
];
tests.invalid = [
...tests.invalid,
Expand Down Expand Up @@ -1220,6 +1256,50 @@ if (__EXPERIMENTAL__) {
`,
errors: [useEventError('onClick')],
},
{
code: normalizeIndent`
Hook.use();
Hook._use();
Hook.useState();
Hook._useState();
Hook.use42();
Hook.useHook();
Hook.use_hook();
`,
errors: [
topLevelError('Hook.use'),
topLevelError('Hook.useState'),
topLevelError('Hook.use42'),
topLevelError('Hook.useHook'),
],
},
{
code: normalizeIndent`
function notAComponent() {
use(promise);
}
`,
errors: [functionError('use', 'notAComponent')],
},
{
code: normalizeIndent`
const text = use(promise);
function App() {
return <Text text={text} />
}
`,
errors: [topLevelError('use')],
},
{
code: normalizeIndent`
class C {
m() {
use(promise);
}
}
`,
errors: [classError('use')],
},
];
}

Expand Down
22 changes: 19 additions & 3 deletions packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/

function isHookName(s) {
if (__EXPERIMENTAL__) {
return s === 'use' || /^use[A-Z0-9]/.test(s);
}
return /^use[A-Z0-9]/.test(s);
}

Expand Down Expand Up @@ -107,6 +110,13 @@ function isUseEventIdentifier(node) {
return false;
}

function isUseIdentifier(node) {
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'use';
}
return false;
}

export default {
meta: {
type: 'problem',
Expand Down Expand Up @@ -458,7 +468,8 @@ export default {

for (const hook of reactHooks) {
// Report an error if a hook may be called more then once.
if (cycled) {
// `use(...)` can be called in loops.
if (cycled && !isUseIdentifier(hook)) {
context.report({
node: hook,
message:
Expand All @@ -479,7 +490,11 @@ export default {
// path segments.
//
// Special case when we think there might be an early return.
if (!cycled && pathsFromStartToEnd !== allPathsFromStartToEnd) {
if (
!cycled &&
pathsFromStartToEnd !== allPathsFromStartToEnd &&
!isUseIdentifier(hook) // `use(...)` can be called conditionally.
) {
const message =
`React Hook "${context.getSource(hook)}" is called ` +
'conditionally. React Hooks must be called in the exact ' +
Expand Down Expand Up @@ -525,7 +540,8 @@ export default {
// anonymous function expressions. Hopefully this is clarifying
// enough in the common case that the incorrect message in
// uncommon cases doesn't matter.
if (isSomewhereInsideComponentOrHook) {
// `use(...)` can be called in callbacks.
if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
const message =
`React Hook "${context.getSource(hook)}" cannot be called ` +
'inside a callback. React Hooks must be called in a ' +
Expand Down

0 comments on commit 3fd9bd8

Please sign in to comment.