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: fe4e7995be379f153a7ef1d1dd28bfa5be785427
Pull Request resolved: #25370
  • Loading branch information
poteto committed Oct 4, 2022
1 parent 64fe791 commit 744b0db
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 4 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,25 @@ 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"} />
}
`,
},
];
tests.invalid = [
...tests.invalid,
Expand Down Expand Up @@ -1220,6 +1236,66 @@ 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`
function App() {
const data = useUserQuery((searchStrings) => {
const query = Promise.all(fetch(user, searchStrings));
return use(query);
});
return (
<ul>
{data.map(d => <li>{d.title}</li>)}
</ul>
);
}
`,
errors: [genericError('use')],
},
{
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
16 changes: 15 additions & 1 deletion 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 @@ -479,7 +489,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

0 comments on commit 744b0db

Please sign in to comment.