mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +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'}],
|
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`
|
code: normalizeIndent`
|
||||||
function MyComponent(props) {
|
function MyComponent(props) {
|
||||||
|
|
@ -1470,6 +1486,38 @@ const tests = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
invalid: [
|
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`
|
code: normalizeIndent`
|
||||||
function MyComponent(props) {
|
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`
|
code: normalizeIndent`
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -8176,6 +8242,48 @@ const testsTypescript = {
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
const [state, setState] = React.useState<number>(0);
|
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(() => {
|
useMemo(() => {
|
||||||
const someNumber: typeof state = 2;
|
const someNumber: typeof state = 2;
|
||||||
console.log(someNumber);
|
console.log(someNumber);
|
||||||
|
|
|
||||||
|
|
@ -61,27 +61,38 @@ const rule = {
|
||||||
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
|
experimental_autoDependenciesHooks: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
create(context: Rule.RuleContext) {
|
create(context: Rule.RuleContext) {
|
||||||
|
const rawOptions = context.options && context.options[0];
|
||||||
|
|
||||||
// Parse the `additionalHooks` regex.
|
// Parse the `additionalHooks` regex.
|
||||||
const additionalHooks =
|
const additionalHooks =
|
||||||
context.options &&
|
rawOptions && rawOptions.additionalHooks
|
||||||
context.options[0] &&
|
? new RegExp(rawOptions.additionalHooks)
|
||||||
context.options[0].additionalHooks
|
|
||||||
? new RegExp(context.options[0].additionalHooks)
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean =
|
const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean =
|
||||||
(context.options &&
|
(rawOptions &&
|
||||||
context.options[0] &&
|
rawOptions.enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
||||||
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
|
||||||
false;
|
false;
|
||||||
|
|
||||||
|
const experimental_autoDependenciesHooks: ReadonlyArray<string> =
|
||||||
|
rawOptions && Array.isArray(rawOptions.experimental_autoDependenciesHooks)
|
||||||
|
? rawOptions.experimental_autoDependenciesHooks
|
||||||
|
: [];
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
additionalHooks,
|
additionalHooks,
|
||||||
|
experimental_autoDependenciesHooks,
|
||||||
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -162,6 +173,7 @@ const rule = {
|
||||||
reactiveHook: Node,
|
reactiveHook: Node,
|
||||||
reactiveHookName: string,
|
reactiveHookName: string,
|
||||||
isEffect: boolean,
|
isEffect: boolean,
|
||||||
|
isAutoDepsHook: boolean,
|
||||||
): void {
|
): void {
|
||||||
if (isEffect && node.async) {
|
if (isEffect && node.async) {
|
||||||
reportProblem({
|
reportProblem({
|
||||||
|
|
@ -649,6 +661,9 @@ const rule = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!declaredDependenciesNode) {
|
if (!declaredDependenciesNode) {
|
||||||
|
if (isAutoDepsHook) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Check if there are any top-level setState() calls.
|
// Check if there are any top-level setState() calls.
|
||||||
// Those tend to lead to infinite loops.
|
// Those tend to lead to infinite loops.
|
||||||
let setStateInsideEffectWithoutDeps: string | null = null;
|
let setStateInsideEffectWithoutDeps: string | null = null;
|
||||||
|
|
@ -711,6 +726,13 @@ const rule = {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
isAutoDepsHook &&
|
||||||
|
declaredDependenciesNode.type === 'Literal' &&
|
||||||
|
declaredDependenciesNode.value === null
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const declaredDependencies: Array<DeclaredDependency> = [];
|
const declaredDependencies: Array<DeclaredDependency> = [];
|
||||||
const externalDependencies = new Set<string>();
|
const externalDependencies = new Set<string>();
|
||||||
|
|
@ -1318,10 +1340,19 @@ const rule = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAutoDepsHook =
|
||||||
|
options.experimental_autoDependenciesHooks.includes(reactiveHookName);
|
||||||
|
|
||||||
// Check the declared dependencies for this reactive hook. If there is no
|
// Check the declared dependencies for this reactive hook. If there is no
|
||||||
// second argument then the reactive callback will re-run on every render.
|
// second argument then the reactive callback will re-run on every render.
|
||||||
// So no need to check for dependency inclusion.
|
// 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.
|
// These are only used for optimization.
|
||||||
if (
|
if (
|
||||||
reactiveHookName === 'useMemo' ||
|
reactiveHookName === 'useMemo' ||
|
||||||
|
|
@ -1355,11 +1386,17 @@ const rule = {
|
||||||
reactiveHook,
|
reactiveHook,
|
||||||
reactiveHookName,
|
reactiveHookName,
|
||||||
isEffect,
|
isEffect,
|
||||||
|
isAutoDepsHook,
|
||||||
);
|
);
|
||||||
return; // Handled
|
return; // Handled
|
||||||
case 'Identifier':
|
case 'Identifier':
|
||||||
if (!declaredDependenciesNode) {
|
if (
|
||||||
// No deps, no problems.
|
!declaredDependenciesNode ||
|
||||||
|
(isAutoDepsHook &&
|
||||||
|
declaredDependenciesNode.type === 'Literal' &&
|
||||||
|
declaredDependenciesNode.value === null)
|
||||||
|
) {
|
||||||
|
// Always runs, no problems.
|
||||||
return; // Handled
|
return; // Handled
|
||||||
}
|
}
|
||||||
// The function passed as a callback is not written inline.
|
// The function passed as a callback is not written inline.
|
||||||
|
|
@ -1408,6 +1445,7 @@ const rule = {
|
||||||
reactiveHook,
|
reactiveHook,
|
||||||
reactiveHookName,
|
reactiveHookName,
|
||||||
isEffect,
|
isEffect,
|
||||||
|
isAutoDepsHook,
|
||||||
);
|
);
|
||||||
return; // Handled
|
return; // Handled
|
||||||
case 'VariableDeclarator':
|
case 'VariableDeclarator':
|
||||||
|
|
@ -1427,6 +1465,7 @@ const rule = {
|
||||||
reactiveHook,
|
reactiveHook,
|
||||||
reactiveHookName,
|
reactiveHookName,
|
||||||
isEffect,
|
isEffect,
|
||||||
|
isAutoDepsHook,
|
||||||
);
|
);
|
||||||
return; // Handled
|
return; // Handled
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user