mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[eslint-plugin-react-hooks] add experimental_autoDependenciesHooks option (#33294)
This commit is contained in:
parent
462d08f9ba
commit
a3abf5f2f8
|
|
@ -515,6 +515,22 @@ const tests = {
|
|||
`,
|
||||
options: [{additionalHooks: 'useCustomEffect'}],
|
||||
},
|
||||
{
|
||||
// behaves like no deps
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useSpecialEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, null);
|
||||
}
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
additionalHooks: 'useSpecialEffect',
|
||||
experimental_autoDependenciesHooks: ['useSpecialEffect'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
|
|
@ -1470,6 +1486,38 @@ const tests = {
|
|||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useSpecialEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, null);
|
||||
}
|
||||
`,
|
||||
options: [{additionalHooks: 'useSpecialEffect'}],
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useSpecialEffect was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies.",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"React Hook useSpecialEffect has a missing dependency: 'props.foo'. Either include it or remove the dependency array.",
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [props.foo]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useSpecialEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
|
|
@ -7821,6 +7869,24 @@ const testsTypescript = {
|
|||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useSpecialEffect(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
setState(prevState => prevState + someNumber);
|
||||
})
|
||||
}
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
additionalHooks: 'useSpecialEffect',
|
||||
experimental_autoDependenciesHooks: ['useSpecialEffect'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function App() {
|
||||
|
|
@ -8176,6 +8242,48 @@ const testsTypescript = {
|
|||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useSpecialEffect(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
setState(prevState => prevState + someNumber + state);
|
||||
}, [])
|
||||
}
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
additionalHooks: 'useSpecialEffect',
|
||||
experimental_autoDependenciesHooks: ['useSpecialEffect'],
|
||||
},
|
||||
],
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useSpecialEffect has a missing dependency: 'state'. " +
|
||||
'Either include it or remove the dependency array. ' +
|
||||
`You can also do a functional update 'setState(s => ...)' ` +
|
||||
`if you only need 'state' in the 'setState' call.`,
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [state]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useSpecialEffect(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
setState(prevState => prevState + someNumber + state);
|
||||
}, [state])
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useMemo(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
console.log(someNumber);
|
||||
|
|
|
|||
|
|
@ -61,27 +61,38 @@ const rule = {
|
|||
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
||||
type: 'boolean',
|
||||
},
|
||||
experimental_autoDependenciesHooks: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
create(context: Rule.RuleContext) {
|
||||
const rawOptions = context.options && context.options[0];
|
||||
|
||||
// Parse the `additionalHooks` regex.
|
||||
const additionalHooks =
|
||||
context.options &&
|
||||
context.options[0] &&
|
||||
context.options[0].additionalHooks
|
||||
? new RegExp(context.options[0].additionalHooks)
|
||||
rawOptions && rawOptions.additionalHooks
|
||||
? new RegExp(rawOptions.additionalHooks)
|
||||
: undefined;
|
||||
|
||||
const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean =
|
||||
(context.options &&
|
||||
context.options[0] &&
|
||||
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
||||
(rawOptions &&
|
||||
rawOptions.enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
||||
false;
|
||||
|
||||
const experimental_autoDependenciesHooks: ReadonlyArray<string> =
|
||||
rawOptions && Array.isArray(rawOptions.experimental_autoDependenciesHooks)
|
||||
? rawOptions.experimental_autoDependenciesHooks
|
||||
: [];
|
||||
|
||||
const options = {
|
||||
additionalHooks,
|
||||
experimental_autoDependenciesHooks,
|
||||
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
||||
};
|
||||
|
||||
|
|
@ -162,6 +173,7 @@ const rule = {
|
|||
reactiveHook: Node,
|
||||
reactiveHookName: string,
|
||||
isEffect: boolean,
|
||||
isAutoDepsHook: boolean,
|
||||
): void {
|
||||
if (isEffect && node.async) {
|
||||
reportProblem({
|
||||
|
|
@ -649,6 +661,9 @@ const rule = {
|
|||
}
|
||||
|
||||
if (!declaredDependenciesNode) {
|
||||
if (isAutoDepsHook) {
|
||||
return;
|
||||
}
|
||||
// Check if there are any top-level setState() calls.
|
||||
// Those tend to lead to infinite loops.
|
||||
let setStateInsideEffectWithoutDeps: string | null = null;
|
||||
|
|
@ -711,6 +726,13 @@ const rule = {
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isAutoDepsHook &&
|
||||
declaredDependenciesNode.type === 'Literal' &&
|
||||
declaredDependenciesNode.value === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const declaredDependencies: Array<DeclaredDependency> = [];
|
||||
const externalDependencies = new Set<string>();
|
||||
|
|
@ -1318,10 +1340,19 @@ const rule = {
|
|||
return;
|
||||
}
|
||||
|
||||
const isAutoDepsHook =
|
||||
options.experimental_autoDependenciesHooks.includes(reactiveHookName);
|
||||
|
||||
// Check the declared dependencies for this reactive hook. If there is no
|
||||
// second argument then the reactive callback will re-run on every render.
|
||||
// So no need to check for dependency inclusion.
|
||||
if (!declaredDependenciesNode && !isEffect) {
|
||||
if (
|
||||
(!declaredDependenciesNode ||
|
||||
(isAutoDepsHook &&
|
||||
declaredDependenciesNode.type === 'Literal' &&
|
||||
declaredDependenciesNode.value === null)) &&
|
||||
!isEffect
|
||||
) {
|
||||
// These are only used for optimization.
|
||||
if (
|
||||
reactiveHookName === 'useMemo' ||
|
||||
|
|
@ -1355,11 +1386,17 @@ const rule = {
|
|||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
isAutoDepsHook,
|
||||
);
|
||||
return; // Handled
|
||||
case 'Identifier':
|
||||
if (!declaredDependenciesNode) {
|
||||
// No deps, no problems.
|
||||
if (
|
||||
!declaredDependenciesNode ||
|
||||
(isAutoDepsHook &&
|
||||
declaredDependenciesNode.type === 'Literal' &&
|
||||
declaredDependenciesNode.value === null)
|
||||
) {
|
||||
// Always runs, no problems.
|
||||
return; // Handled
|
||||
}
|
||||
// The function passed as a callback is not written inline.
|
||||
|
|
@ -1408,6 +1445,7 @@ const rule = {
|
|||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
isAutoDepsHook,
|
||||
);
|
||||
return; // Handled
|
||||
case 'VariableDeclarator':
|
||||
|
|
@ -1427,6 +1465,7 @@ const rule = {
|
|||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
isAutoDepsHook,
|
||||
);
|
||||
return; // Handled
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user