8253 lines
246 KiB
JavaScript
8253 lines
246 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const ESLintTester = require('eslint').RuleTester;
|
|
const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
|
|
const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['exhaustive-deps'];
|
|
|
|
/**
|
|
* A string template tag that removes padding from the left side of multi-line strings
|
|
* @param {Array} strings array of code strings (only one expected)
|
|
*/
|
|
function normalizeIndent(strings) {
|
|
const codeLines = strings[0].split('\n');
|
|
const leftPadding = codeLines[1].match(/\s+/)[0];
|
|
return codeLines.map(line => line.slice(leftPadding.length)).join('\n');
|
|
}
|
|
|
|
// ***************************************************
|
|
// For easier local testing, you can add to any case:
|
|
// {
|
|
// skip: true,
|
|
// --or--
|
|
// only: true,
|
|
// ...
|
|
// }
|
|
// ***************************************************
|
|
|
|
// Tests that are valid/invalid across all parsers
|
|
const tests = {
|
|
valid: [
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
});
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
const local = {};
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// OK because `props` wasn't defined.
|
|
// We don't technically know if `props` is supposed
|
|
// to be an import that hasn't been added yet, or
|
|
// a component-level variable. Ignore it until it
|
|
// gets defined (a different rule would flag it anyway).
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
{
|
|
const local2 = {};
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
});
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = someFunc();
|
|
{
|
|
const local2 = someFunc();
|
|
useCallback(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local1, local2]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = someFunc();
|
|
function MyNestedComponent() {
|
|
const local2 = someFunc();
|
|
useCallback(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local2]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(local);
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
console.log(unresolved);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [,,,local,,,]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent({ foo }) {
|
|
useEffect(() => {
|
|
console.log(foo.length);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent({ foo }) {
|
|
useEffect(() => {
|
|
console.log(foo.length);
|
|
console.log(foo.slice(0));
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return history.listen();
|
|
}, [history]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because they have meaning without deps.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {});
|
|
useLayoutEffect(() => {});
|
|
useImperativeHandle(props.innerRef, () => {});
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props.bar, props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props.foo, props.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
console.log(local);
|
|
}, [props.foo, props.bar, local]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// [props, props.foo] is technically unnecessary ('props' covers 'props.foo').
|
|
// However, it's valid for effects to over-specify their deps.
|
|
// So we don't warn about this. We *would* warn about useMemo/useCallback.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props, props.foo]);
|
|
|
|
let color = someFunc();
|
|
useEffect(() => {
|
|
console.log(props.foo.bar.baz);
|
|
console.log(color);
|
|
}, [props.foo, props.foo.bar.baz, color]);
|
|
}
|
|
`,
|
|
},
|
|
// Nullish coalescing and optional chaining
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo?.bar?.baz ?? null);
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo?.bar);
|
|
}, [props.foo?.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo?.bar);
|
|
}, [props.foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo.bar);
|
|
}, [props.foo?.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo.bar);
|
|
console.log(props.foo?.bar);
|
|
}, [props.foo?.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo.bar);
|
|
console.log(props.foo?.bar);
|
|
}, [props.foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.foo?.bar);
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo?.toString());
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useMemo(() => {
|
|
console.log(props.foo?.toString());
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.toString());
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo.bar?.toString());
|
|
}, [props.foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar?.toString());
|
|
}, [props.foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo.bar.toString());
|
|
}, [props?.foo?.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar?.baz);
|
|
}, [props?.foo.bar?.baz]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myEffect = () => {
|
|
// Doesn't use anything
|
|
};
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
const local = {};
|
|
function MyComponent() {
|
|
const myEffect = () => {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
const local = {};
|
|
function MyComponent() {
|
|
function myEffect() {
|
|
console.log(local);
|
|
}
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
function myEffect() {
|
|
console.log(local);
|
|
}
|
|
useEffect(myEffect, [local]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
function myEffect() {
|
|
console.log(global);
|
|
}
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
const local = {};
|
|
function MyComponent() {
|
|
const myEffect = () => {
|
|
otherThing()
|
|
}
|
|
const otherThing = () => {
|
|
console.log(local);
|
|
}
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because even though we don't inspect the function itself,
|
|
// at least it's passed as a dependency.
|
|
code: normalizeIndent`
|
|
function MyComponent({delay}) {
|
|
const local = {};
|
|
const myEffect = debounce(() => {
|
|
console.log(local);
|
|
}, delay);
|
|
useEffect(myEffect, [myEffect]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({myEffect}) {
|
|
useEffect(myEffect, [,myEffect]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({myEffect}) {
|
|
useEffect(myEffect, [,myEffect,,]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
let local = {};
|
|
function myEffect() {
|
|
console.log(local);
|
|
}
|
|
function MyComponent() {
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({myEffect}) {
|
|
useEffect(myEffect, [myEffect]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because has no deps.
|
|
code: normalizeIndent`
|
|
function MyComponent({myEffect}) {
|
|
useEffect(myEffect);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
});
|
|
}
|
|
`,
|
|
options: [{additionalHooks: 'useCustomEffect'}],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
options: [{additionalHooks: 'useCustomEffect'}],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
options: [{additionalHooks: 'useAnotherEffect'}],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useWithoutEffectSuffix(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
return renderHelperConfusedWithEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we don't care about hooks outside of components.
|
|
code: normalizeIndent`
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, []);
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we don't care about hooks outside of components.
|
|
code: normalizeIndent`
|
|
const local1 = {};
|
|
{
|
|
const local2 = {};
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, [ref]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ maybeRef2, foo }) {
|
|
const definitelyRef1 = useRef();
|
|
const definitelyRef2 = useRef();
|
|
const maybeRef1 = useSomeOtherRefyThing();
|
|
const [state1, setState1] = useState();
|
|
const [state2, setState2] = React.useState();
|
|
const [state3, dispatch1] = useReducer();
|
|
const [state4, dispatch2] = React.useReducer();
|
|
const [state5, maybeSetState] = useFunnyState();
|
|
const [state6, maybeDispatch] = useFunnyReducer();
|
|
const [isPending1] = useTransition();
|
|
const [isPending2, startTransition2] = useTransition();
|
|
const [isPending3] = React.useTransition();
|
|
const [isPending4, startTransition4] = React.useTransition();
|
|
const mySetState = useCallback(() => {}, []);
|
|
let myDispatch = useCallback(() => {}, []);
|
|
|
|
useEffect(() => {
|
|
// Known to be static
|
|
console.log(definitelyRef1.current);
|
|
console.log(definitelyRef2.current);
|
|
console.log(maybeRef1.current);
|
|
console.log(maybeRef2.current);
|
|
setState1();
|
|
setState2();
|
|
dispatch1();
|
|
dispatch2();
|
|
startTransition1();
|
|
startTransition2();
|
|
startTransition3();
|
|
startTransition4();
|
|
|
|
// Dynamic
|
|
console.log(state1);
|
|
console.log(state2);
|
|
console.log(state3);
|
|
console.log(state4);
|
|
console.log(state5);
|
|
console.log(state6);
|
|
console.log(isPending2);
|
|
console.log(isPending4);
|
|
mySetState();
|
|
myDispatch();
|
|
|
|
// Not sure; assume dynamic
|
|
maybeSetState();
|
|
maybeDispatch();
|
|
}, [
|
|
// Dynamic
|
|
state1, state2, state3, state4, state5, state6,
|
|
maybeRef1, maybeRef2,
|
|
isPending2, isPending4,
|
|
|
|
// Not sure; assume dynamic
|
|
mySetState, myDispatch,
|
|
maybeSetState, maybeDispatch
|
|
|
|
// In this test, we don't specify static deps.
|
|
// That should be okay.
|
|
]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ maybeRef2 }) {
|
|
const definitelyRef1 = useRef();
|
|
const definitelyRef2 = useRef();
|
|
const maybeRef1 = useSomeOtherRefyThing();
|
|
|
|
const [state1, setState1] = useState();
|
|
const [state2, setState2] = React.useState();
|
|
const [state3, dispatch1] = useReducer();
|
|
const [state4, dispatch2] = React.useReducer();
|
|
|
|
const [state5, maybeSetState] = useFunnyState();
|
|
const [state6, maybeDispatch] = useFunnyReducer();
|
|
|
|
const mySetState = useCallback(() => {}, []);
|
|
let myDispatch = useCallback(() => {}, []);
|
|
|
|
useEffect(() => {
|
|
// Known to be static
|
|
console.log(definitelyRef1.current);
|
|
console.log(definitelyRef2.current);
|
|
console.log(maybeRef1.current);
|
|
console.log(maybeRef2.current);
|
|
setState1();
|
|
setState2();
|
|
dispatch1();
|
|
dispatch2();
|
|
|
|
// Dynamic
|
|
console.log(state1);
|
|
console.log(state2);
|
|
console.log(state3);
|
|
console.log(state4);
|
|
console.log(state5);
|
|
console.log(state6);
|
|
mySetState();
|
|
myDispatch();
|
|
|
|
// Not sure; assume dynamic
|
|
maybeSetState();
|
|
maybeDispatch();
|
|
}, [
|
|
// Dynamic
|
|
state1, state2, state3, state4, state5, state6,
|
|
maybeRef1, maybeRef2,
|
|
|
|
// Not sure; assume dynamic
|
|
mySetState, myDispatch,
|
|
maybeSetState, maybeDispatch,
|
|
|
|
// In this test, we specify static deps.
|
|
// That should be okay too!
|
|
definitelyRef1, definitelyRef2, setState1, setState2, dispatch1, dispatch2
|
|
]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
const MyComponent = forwardRef((props, ref) => {
|
|
useImperativeHandle(ref, () => ({
|
|
focus() {
|
|
alert(props.hello);
|
|
}
|
|
}))
|
|
});
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
const MyComponent = forwardRef((props, ref) => {
|
|
useImperativeHandle(ref, () => ({
|
|
focus() {
|
|
alert(props.hello);
|
|
}
|
|
}), [props.hello])
|
|
});
|
|
`,
|
|
},
|
|
{
|
|
// This is not ideal but warning would likely create
|
|
// too many false positives. We do, however, prevent
|
|
// direct assignments.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let obj = someFunc();
|
|
useEffect(() => {
|
|
obj.foo = true;
|
|
}, [obj]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let foo = {}
|
|
useEffect(() => {
|
|
foo.bar.baz = 43;
|
|
}, [foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we assign ref.current
|
|
// ourselves. Therefore it's likely not
|
|
// a ref managed by React.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef.current = {};
|
|
return () => {
|
|
console.log(myRef.current.toString())
|
|
};
|
|
}, []);
|
|
return <div />;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we assign ref.current
|
|
// ourselves. Therefore it's likely not
|
|
// a ref managed by React.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef.current = {};
|
|
return () => {
|
|
console.log(myRef?.current?.toString())
|
|
};
|
|
}, []);
|
|
return <div />;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we assign ref.current
|
|
// ourselves. Therefore it's likely not
|
|
// a ref managed by React.
|
|
code: normalizeIndent`
|
|
function useMyThing(myRef) {
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef.current = {};
|
|
return () => {
|
|
console.log(myRef.current.toString())
|
|
};
|
|
}, [myRef]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because the ref is captured.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
const node = myRef.current;
|
|
node.addEventListener('mousemove', handleMove);
|
|
return () => node.removeEventListener('mousemove', handleMove);
|
|
}, []);
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because the ref is captured.
|
|
code: normalizeIndent`
|
|
function useMyThing(myRef) {
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
const node = myRef.current;
|
|
node.addEventListener('mousemove', handleMove);
|
|
return () => node.removeEventListener('mousemove', handleMove);
|
|
}, [myRef]);
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because it's not an effect.
|
|
code: normalizeIndent`
|
|
function useMyThing(myRef) {
|
|
useCallback(() => {
|
|
const handleMouse = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMouse);
|
|
myRef.current.addEventListener('mousein', handleMouse);
|
|
return function() {
|
|
setTimeout(() => {
|
|
myRef.current.removeEventListener('mousemove', handleMouse);
|
|
myRef.current.removeEventListener('mousein', handleMouse);
|
|
});
|
|
}
|
|
}, [myRef]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we read ref.current in a function that isn't cleanup.
|
|
code: normalizeIndent`
|
|
function useMyThing() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {
|
|
console.log(myRef.current)
|
|
};
|
|
window.addEventListener('mousemove', handleMove);
|
|
return () => window.removeEventListener('mousemove', handleMove);
|
|
}, []);
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because we read ref.current in a function that isn't cleanup.
|
|
code: normalizeIndent`
|
|
function useMyThing() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {
|
|
return () => window.removeEventListener('mousemove', handleMove);
|
|
};
|
|
window.addEventListener('mousemove', handleMove);
|
|
return () => {};
|
|
}, []);
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid because it's a primitive constant.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = 42;
|
|
const local2 = '42';
|
|
const local3 = null;
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
console.log(local3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// It's not a mistake to specify constant values though.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = 42;
|
|
const local2 = '42';
|
|
const local3 = null;
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
console.log(local3);
|
|
}, [local1, local2, local3]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// It is valid for effects to over-specify their deps.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = props.local;
|
|
useEffect(() => {}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Valid even though activeTab is "unused".
|
|
// We allow over-specifying deps for effects, but not callbacks or memo.
|
|
code: normalizeIndent`
|
|
function Foo({ activeTab }) {
|
|
useEffect(() => {
|
|
window.scrollTo(0, 0);
|
|
}, [activeTab]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// It is valid to specify broader effect deps than strictly necessary.
|
|
// Don't warn for this.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props]);
|
|
useEffect(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo]);
|
|
useEffect(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo.bar]);
|
|
useEffect(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// It is *also* valid to specify broader memo/callback deps than strictly necessary.
|
|
// Don't warn for this either.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props]);
|
|
const fn2 = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo]);
|
|
const fn3 = useMemo(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo.bar]);
|
|
const fn4 = useMemo(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Declaring handleNext is optional because
|
|
// it doesn't use anything in the function scope.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext1() {
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = () => {
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function() {
|
|
console.log('hello');
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Declaring handleNext is optional because
|
|
// it doesn't use anything in the function scope.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext() {
|
|
console.log('hello');
|
|
}
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Declaring handleNext is optional because
|
|
// everything they use is fully static.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * 100;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(foo(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(value);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useInterval(callback, delay) {
|
|
const savedCallback = useRef();
|
|
useEffect(() => {
|
|
savedCallback.current = callback;
|
|
});
|
|
useEffect(() => {
|
|
function tick() {
|
|
savedCallback.current();
|
|
}
|
|
if (delay !== null) {
|
|
let id = setInterval(tick, delay);
|
|
return () => clearInterval(id);
|
|
}
|
|
}, [delay]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
const [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(c => c + 1);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter(unstableProp) {
|
|
let [count, setCount] = useState(0);
|
|
setCount = unstableProp
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(c => c + 1);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [setCount]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
const [count, setCount] = useState(0);
|
|
|
|
function tick() {
|
|
setCount(c => c + 1);
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
tick();
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
const [count, dispatch] = useReducer((state, action) => {
|
|
if (action === 'inc') {
|
|
return state + 1;
|
|
}
|
|
}, 0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
dispatch('inc');
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
const [count, dispatch] = useReducer((state, action) => {
|
|
if (action === 'inc') {
|
|
return state + 1;
|
|
}
|
|
}, 0);
|
|
|
|
const tick = () => {
|
|
dispatch('inc');
|
|
};
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(tick, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
// Regression test for a crash
|
|
code: normalizeIndent`
|
|
function Podcasts() {
|
|
useEffect(() => {
|
|
setPodcasts([]);
|
|
}, []);
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function withFetch(fetchPodcasts) {
|
|
return function Podcasts({ id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [id]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Podcasts({ id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
function doFetch({ fetchPodcasts }) {
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}
|
|
doFetch({ fetchPodcasts: API.fetchPodcasts });
|
|
}, [id]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
|
|
function increment(x) {
|
|
return x + 1;
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
|
|
function increment(x) {
|
|
return x + 1;
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => increment(count));
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
import increment from './increment';
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function withStuff(increment) {
|
|
return function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function App() {
|
|
const [query, setQuery] = useState('react');
|
|
const [state, setState] = useState(null);
|
|
useEffect(() => {
|
|
let ignore = false;
|
|
fetchSomething();
|
|
async function fetchSomething() {
|
|
const result = await (await fetch('http://hn.algolia.com/api/v1/search?query=' + query)).json();
|
|
if (!ignore) setState(result);
|
|
}
|
|
return () => { ignore = true; };
|
|
}, [query]);
|
|
return (
|
|
<>
|
|
<input value={query} onChange={e => setQuery(e.target.value)} />
|
|
{JSON.stringify(state)}
|
|
</>
|
|
);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Example() {
|
|
const foo = useCallback(() => {
|
|
foo();
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Example({ prop }) {
|
|
const foo = useCallback(() => {
|
|
if (prop) {
|
|
foo();
|
|
}
|
|
}, [prop]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Hello() {
|
|
const [state, setState] = useState(0);
|
|
useEffect(() => {
|
|
const handleResize = () => setState(window.innerWidth);
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
});
|
|
}
|
|
`,
|
|
},
|
|
// Ignore arguments keyword for arrow functions.
|
|
{
|
|
code: normalizeIndent`
|
|
function Example() {
|
|
useEffect(() => {
|
|
arguments
|
|
}, [])
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Example() {
|
|
useEffect(() => {
|
|
const bar = () => {
|
|
arguments;
|
|
};
|
|
bar();
|
|
}, [])
|
|
}
|
|
`,
|
|
},
|
|
// Regression test.
|
|
{
|
|
code: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props.upperViewHeight;
|
|
}, [props.upperViewHeight]);
|
|
}
|
|
`,
|
|
},
|
|
// Regression test.
|
|
{
|
|
code: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props?.upperViewHeight;
|
|
}, [props?.upperViewHeight]);
|
|
}
|
|
`,
|
|
},
|
|
// Regression test.
|
|
{
|
|
code: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props?.upperViewHeight;
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useFoo(foo){
|
|
return useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useFoo(){
|
|
const foo = "hi!";
|
|
return useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useFoo(){
|
|
let {foo} = {foo: 1};
|
|
return useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useFoo(){
|
|
let [foo] = [1];
|
|
return useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useFoo() {
|
|
const foo = "fine";
|
|
if (true) {
|
|
// Shadowed variable with constant construction in a nested scope is fine.
|
|
const foo = {};
|
|
}
|
|
return useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({foo}) {
|
|
return useMemo(() => foo, [foo])
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const foo = true ? "fine" : "also fine";
|
|
return useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
invalid: [
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.toString());
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback 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) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.toString());
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar.baz);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'props.foo?.bar.baz'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo?.bar.baz]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar.baz);
|
|
}, [props.foo?.bar.baz]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar?.baz);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'props.foo?.bar?.baz'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo?.bar?.baz]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar?.baz);
|
|
}, [props.foo?.bar?.baz]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar.toString());
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'props.foo?.bar'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo?.bar]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCallback(() => {
|
|
console.log(props.foo?.bar.toString());
|
|
}, [props.foo?.bar]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter(unstableProp) {
|
|
let [count, setCount] = useState(0);
|
|
setCount = unstableProp
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(c => c + 1);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'setCount'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [setCount]',
|
|
output: normalizeIndent`
|
|
function Counter(unstableProp) {
|
|
let [count, setCount] = useState(0);
|
|
setCount = unstableProp
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(c => c + 1);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [setCount]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Note: we *could* detect it's a primitive and never assigned
|
|
// even though it's not a constant -- but we currently don't.
|
|
// So this is an error.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
let local = 42;
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
let local = 42;
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regexes are literals but potentially stateful.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = /foo/;
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = /foo/;
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Invalid because they don't have a meaning without deps.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const value = useMemo(() => { return 2*2; });
|
|
const fn = useCallback(() => { alert('foo'); });
|
|
}
|
|
`,
|
|
// We don't know what you meant.
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useMemo does nothing when called with only one argument. ' +
|
|
'Did you forget to pass an array of dependencies?',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useCallback does nothing when called with only one argument. ' +
|
|
'Did you forget to pass an array of dependencies?',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Invalid because they don't have a meaning without deps.
|
|
code: normalizeIndent`
|
|
function MyComponent({ fn1, fn2 }) {
|
|
const value = useMemo(fn1);
|
|
const fn = useCallback(fn2);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useMemo does nothing when called with only one argument. ' +
|
|
'Did you forget to pass an array of dependencies?',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useCallback does nothing when called with only one argument. ' +
|
|
'Did you forget to pass an array of dependencies?',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect()
|
|
useLayoutEffect()
|
|
useCallback()
|
|
useMemo()
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect requires an effect callback. ' +
|
|
'Did you forget to pass a callback to the hook?',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useLayoutEffect requires an effect callback. ' +
|
|
'Did you forget to pass a callback to the hook?',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useCallback requires an effect callback. ' +
|
|
'Did you forget to pass a callback to the hook?',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useMemo requires an effect callback. ' +
|
|
'Did you forget to pass a callback to the hook?',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
if (true) {
|
|
console.log(local);
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
if (true) {
|
|
console.log(local);
|
|
}
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
try {
|
|
console.log(local);
|
|
} finally {}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
try {
|
|
console.log(local);
|
|
} finally {}
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
function inner() {
|
|
console.log(local);
|
|
}
|
|
inner();
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
function inner() {
|
|
console.log(local);
|
|
}
|
|
inner();
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = someFunc();
|
|
{
|
|
const local2 = someFunc();
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, []);
|
|
}
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'local1' and 'local2'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local1, local2]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = someFunc();
|
|
{
|
|
const local2 = someFunc();
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local1, local2]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
const local2 = {};
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local1]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local2'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local1, local2]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
const local2 = {};
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local1, local2]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
const local2 = {};
|
|
useMemo(() => {
|
|
console.log(local1);
|
|
}, [local1, local2]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useMemo has an unnecessary dependency: 'local2'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local1]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
const local2 = {};
|
|
useMemo(() => {
|
|
console.log(local1);
|
|
}, [local1]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = someFunc();
|
|
function MyNestedComponent() {
|
|
const local2 = {};
|
|
useCallback(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local1]);
|
|
}
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'local2'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
"Outer scope values like 'local1' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local2]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = someFunc();
|
|
function MyNestedComponent() {
|
|
const local2 = {};
|
|
useCallback(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
}, [local2]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
console.log(local);
|
|
}, [local, local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a duplicate dependency: 'local'. " +
|
|
'Either omit it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useCallback(() => {}, [window]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'window'. " +
|
|
'Either exclude it or remove the dependency array. ' +
|
|
"Outer scope values like 'window' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
useCallback(() => {}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// It is not valid for useCallback to specify extraneous deps
|
|
// because it doesn't serve as a side effect trigger unlike useEffect.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let local = props.foo;
|
|
useCallback(() => {}, [local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'local'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let local = props.foo;
|
|
useCallback(() => {}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return history.listen();
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'history'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [history]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return history.listen();
|
|
}, [history]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return [
|
|
history.foo.bar[2].dobedo.listen(),
|
|
history.foo.bar().dobedo.listen[2]
|
|
];
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'history.foo'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [history.foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return [
|
|
history.foo.bar[2].dobedo.listen(),
|
|
history.foo.bar().dobedo.listen[2]
|
|
];
|
|
}, [history.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return [
|
|
history?.foo
|
|
];
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'history?.foo'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [history?.foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ history }) {
|
|
useEffect(() => {
|
|
return [
|
|
history?.foo
|
|
];
|
|
}, [history?.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect(() => {}, ['foo']);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
// Don't assume user meant `foo` because it's not used in the effect.
|
|
"The 'foo' literal is not a valid dependency because it never changes. " +
|
|
'You can safely remove it.',
|
|
// TODO: provide suggestion.
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ foo, bar, baz }) {
|
|
useEffect(() => {
|
|
console.log(foo, bar, baz);
|
|
}, ['foo', 'bar']);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [bar, baz, foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ foo, bar, baz }) {
|
|
useEffect(() => {
|
|
console.log(foo, bar, baz);
|
|
}, [bar, baz, foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"The 'foo' literal is not a valid dependency because it never changes. " +
|
|
'Did you mean to include foo in the array instead?',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'bar' literal is not a valid dependency because it never changes. " +
|
|
'Did you mean to include bar in the array instead?',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ foo, bar, baz }) {
|
|
useEffect(() => {
|
|
console.log(foo, bar, baz);
|
|
}, [42, false, null]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'bar', 'baz', and 'foo'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [bar, baz, foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ foo, bar, baz }) {
|
|
useEffect(() => {
|
|
console.log(foo, bar, baz);
|
|
}, [bar, baz, foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'The 42 literal is not a valid dependency because it never changes. You can safely remove it.',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'The false literal is not a valid dependency because it never changes. You can safely remove it.',
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
'The null literal is not a valid dependency because it never changes. You can safely remove it.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const dependencies = [];
|
|
useEffect(() => {}, dependencies);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect 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.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const dependencies = [local];
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, dependencies);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect 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.',
|
|
// TODO: should this autofix or bail out?
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const dependencies = [local];
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const dependencies = [local];
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [...dependencies]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const dependencies = [local];
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useEffect has a spread element in its dependency array. ' +
|
|
"This means we can't statically verify whether you've passed the " +
|
|
'correct dependencies.',
|
|
// TODO: should this autofix or bail out?
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = someFunc();
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local, ...dependencies]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has a spread element in its dependency array. ' +
|
|
"This means we can't statically verify whether you've passed the " +
|
|
'correct dependencies.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [computeCacheKey(local)]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
// TODO: I'm not sure this is a good idea.
|
|
// Maybe bail out?
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.items[0]);
|
|
}, [props.items[0]]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props.items'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.items]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.items[0]);
|
|
}, [props.items]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.items[0]);
|
|
}, [props.items, props.items[0]]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
// TODO: ideally suggestion would remove the bad expression?
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ items }) {
|
|
useEffect(() => {
|
|
console.log(items[0]);
|
|
}, [items[0]]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'items'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [items]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ items }) {
|
|
useEffect(() => {
|
|
console.log(items[0]);
|
|
}, [items]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ items }) {
|
|
useEffect(() => {
|
|
console.log(items[0]);
|
|
}, [items, items[0]]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
// TODO: ideally suggeston would remove the bad expression?
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// It is not valid for useCallback to specify extraneous deps
|
|
// because it doesn't serve as a side effect trigger unlike useEffect.
|
|
// However, we generally allow specifying *broader* deps as escape hatch.
|
|
// So while [props, props.foo] is unnecessary, 'props' wins here as the
|
|
// broader one, and this is why 'props.foo' is reported as unnecessary.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props, props.foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'props.foo'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Since we don't have 'props' in the list, we'll suggest narrow dependencies.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has missing dependencies: 'props.bar' and 'props.foo'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.bar, props.foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props.bar, props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Effects are allowed to over-specify deps. We'll complain about missing
|
|
// 'local', but we won't remove the already-specified 'local.id' from your list.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {id: 42};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local.id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local, local.id]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {id: 42};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local, local.id]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Callbacks are not allowed to over-specify deps. So we'll complain about missing
|
|
// 'local' and we will also *remove* 'local.id' from your list.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {id: 42};
|
|
const fn = useCallback(() => {
|
|
console.log(local);
|
|
}, [local.id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {id: 42};
|
|
const fn = useCallback(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Callbacks are not allowed to over-specify deps. So we'll complain about
|
|
// the unnecessary 'local.id'.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {id: 42};
|
|
const fn = useCallback(() => {
|
|
console.log(local);
|
|
}, [local.id, local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'local.id'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {id: 42};
|
|
const fn = useCallback(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'props.foo.bar.baz'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo.bar.baz]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let color = {}
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
console.log(color);
|
|
}, [props.foo, props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'color'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [color, props.foo.bar.baz]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let color = {}
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
console.log(color);
|
|
}, [color, props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Callbacks are not allowed to over-specify deps. So one of these is extra.
|
|
// However, it *is* allowed to specify broader deps then strictly necessary.
|
|
// So in this case we ask you to remove 'props.foo.bar.baz' because 'props.foo'
|
|
// already covers it, and having both is unnecessary.
|
|
// TODO: maybe consider suggesting a narrower one by default in these cases.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo.bar.baz, props.foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'props.foo.bar.baz'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
console.log(props.foo.fizz.bizz);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has missing dependencies: 'props.foo.bar.baz' and 'props.foo.fizz.bizz'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo.bar.baz, props.foo.fizz.bizz]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar.baz);
|
|
console.log(props.foo.fizz.bizz);
|
|
}, [props.foo.bar.baz, props.foo.fizz.bizz]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Normally we allow specifying deps too broadly.
|
|
// So we'd be okay if 'props.foo.bar' was there rather than 'props.foo.bar.baz'.
|
|
// However, 'props.foo.bar.baz' is missing. So we know there is a mistake.
|
|
// When we're sure there is a mistake, for callbacks we will rebuild the list
|
|
// from scratch. This will set the user on a better path by default.
|
|
// This is why we end up with just 'props.foo.bar', and not them both.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar);
|
|
}, [props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'props.foo.bar'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo.bar]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props.foo.bar);
|
|
}, [props.foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props);
|
|
console.log(props.hello);
|
|
}, [props.foo.bar.baz]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const fn = useCallback(() => {
|
|
console.log(props);
|
|
console.log(props.hello);
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local, local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a duplicate dependency: 'local'. " +
|
|
'Either omit it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
useCallback(() => {
|
|
const local1 = {};
|
|
console.log(local1);
|
|
}, [local1]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'local1'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
useCallback(() => {
|
|
const local1 = {};
|
|
console.log(local1);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
useCallback(() => {}, [local1]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'local1'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = {};
|
|
useCallback(() => {}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'props.bar' and 'props.foo'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.bar, props.foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
}, [props.bar, props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let a, b, c, d, e, f, g;
|
|
useEffect(() => {
|
|
console.log(b, e, d, c, a, g, f);
|
|
}, [c, a, g]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
|
|
'Either include them or remove the dependency array.',
|
|
// Don't alphabetize if it wasn't alphabetized in the first place.
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [c, a, g, b, e, d, f]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let a, b, c, d, e, f, g;
|
|
useEffect(() => {
|
|
console.log(b, e, d, c, a, g, f);
|
|
}, [c, a, g, b, e, d, f]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let a, b, c, d, e, f, g;
|
|
useEffect(() => {
|
|
console.log(b, e, d, c, a, g, f);
|
|
}, [a, c, g]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'b', 'd', 'e', and 'f'. " +
|
|
'Either include them or remove the dependency array.',
|
|
// Alphabetize if it was alphabetized.
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [a, b, c, d, e, f, g]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let a, b, c, d, e, f, g;
|
|
useEffect(() => {
|
|
console.log(b, e, d, c, a, g, f);
|
|
}, [a, b, c, d, e, f, g]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let a, b, c, d, e, f, g;
|
|
useEffect(() => {
|
|
console.log(b, e, d, c, a, g, f);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'a', 'b', 'c', 'd', 'e', 'f', and 'g'. " +
|
|
'Either include them or remove the dependency array.',
|
|
// Alphabetize if it was empty.
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [a, b, c, d, e, f, g]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let a, b, c, d, e, f, g;
|
|
useEffect(() => {
|
|
console.log(b, e, d, c, a, g, f);
|
|
}, [a, b, c, d, e, f, g]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'local', 'props.bar', and 'props.foo'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local, props.bar, props.foo]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
console.log(local);
|
|
}, [local, props.bar, props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
console.log(local);
|
|
}, [props]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local, props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
console.log(props.bar);
|
|
console.log(local);
|
|
}, [local, props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useCallback 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useMemo 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook React.useEffect 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook React.useCallback 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook React.useMemo 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) {
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useMemo(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCallback(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useMemo(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
React.notReactiveHook(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
options: [{additionalHooks: 'useCustomEffect'}],
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCustomEffect 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) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useEffect 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) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook React.useEffect 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) {
|
|
useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
useEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
React.useEffect(() => {
|
|
console.log(props.foo);
|
|
}, [props.foo]);
|
|
React.useCustomEffect(() => {
|
|
console.log(props.foo);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [a ? local : b]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
// TODO: should we bail out instead?
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [a && local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
// TODO: should we bail out instead?
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {}, [props?.attribute.method()]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {}, [props.method()]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has a complex expression in the dependency array. ' +
|
|
'Extract it to a separate variable so it can be statically checked.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
const [state, setState] = useState();
|
|
useEffect(() => {
|
|
ref.current = {};
|
|
setState(state + 1);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect 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 ref = useRef();
|
|
const [state, setState] = useState();
|
|
useEffect(() => {
|
|
ref.current = {};
|
|
setState(state + 1);
|
|
}, [state]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
const [state, setState] = useState();
|
|
useEffect(() => {
|
|
ref.current = {};
|
|
setState(state + 1);
|
|
}, [ref]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect 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.`,
|
|
// We don't ask to remove static deps but don't add them either.
|
|
// Don't suggest removing "ref" (it's fine either way)
|
|
// but *do* add "state". *Don't* add "setState" ourselves.
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [ref, state]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
const [state, setState] = useState();
|
|
useEffect(() => {
|
|
ref.current = {};
|
|
setState(state + 1);
|
|
}, [ref, state]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1.current.focus();
|
|
console.log(ref2.current.textContent);
|
|
alert(props.someOtherRefs.current.innerHTML);
|
|
fetch(props.color);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'props.color' and 'props.someOtherRefs'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.color, props.someOtherRefs]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1.current.focus();
|
|
console.log(ref2.current.textContent);
|
|
alert(props.someOtherRefs.current.innerHTML);
|
|
fetch(props.color);
|
|
}, [props.color, props.someOtherRefs]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1.current.focus();
|
|
console.log(ref2.current.textContent);
|
|
alert(props.someOtherRefs.current.innerHTML);
|
|
fetch(props.color);
|
|
}, [ref1.current, ref2.current, props.someOtherRefs, props.color]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Mutable values like 'ref1.current' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.someOtherRefs, props.color]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1.current.focus();
|
|
console.log(ref2.current.textContent);
|
|
alert(props.someOtherRefs.current.innerHTML);
|
|
fetch(props.color);
|
|
}, [props.someOtherRefs, props.color]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1?.current?.focus();
|
|
console.log(ref2?.current?.textContent);
|
|
alert(props.someOtherRefs.current.innerHTML);
|
|
fetch(props.color);
|
|
}, [ref1?.current, ref2?.current, props.someOtherRefs, props.color]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Mutable values like 'ref1.current' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.someOtherRefs, props.color]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1?.current?.focus();
|
|
console.log(ref2?.current?.textContent);
|
|
alert(props.someOtherRefs.current.innerHTML);
|
|
fetch(props.color);
|
|
}, [props.someOtherRefs, props.color]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, [ref.current]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
|
|
'Either exclude it or remove the dependency array. ' +
|
|
"Mutable values like 'ref.current' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ activeTab }) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1.current.scrollTop = 0;
|
|
ref2.current.scrollTop = 0;
|
|
}, [ref1.current, ref2.current, activeTab]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has unnecessary dependencies: 'ref1.current' and 'ref2.current'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Mutable values like 'ref1.current' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [activeTab]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ activeTab }) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
useEffect(() => {
|
|
ref1.current.scrollTop = 0;
|
|
ref2.current.scrollTop = 0;
|
|
}, [activeTab]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ activeTab, initY }) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
const fn = useCallback(() => {
|
|
ref1.current.scrollTop = initY;
|
|
ref2.current.scrollTop = initY;
|
|
}, [ref1.current, ref2.current, activeTab, initY]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has unnecessary dependencies: 'activeTab', 'ref1.current', and 'ref2.current'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Mutable values like 'ref1.current' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [initY]',
|
|
output: normalizeIndent`
|
|
function MyComponent({ activeTab, initY }) {
|
|
const ref1 = useRef();
|
|
const ref2 = useRef();
|
|
const fn = useCallback(() => {
|
|
ref1.current.scrollTop = initY;
|
|
ref2.current.scrollTop = initY;
|
|
}, [initY]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, [ref.current, ref]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has an unnecessary dependency: 'ref.current'. " +
|
|
'Either exclude it or remove the dependency array. ' +
|
|
"Mutable values like 'ref.current' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [ref]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef();
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, [ref]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
const MyComponent = forwardRef((props, ref) => {
|
|
useImperativeHandle(ref, () => ({
|
|
focus() {
|
|
alert(props.hello);
|
|
}
|
|
}), [])
|
|
});
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useImperativeHandle has a missing dependency: 'props.hello'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.hello]',
|
|
output: normalizeIndent`
|
|
const MyComponent = forwardRef((props, ref) => {
|
|
useImperativeHandle(ref, () => ({
|
|
focus() {
|
|
alert(props.hello);
|
|
}
|
|
}), [props.hello])
|
|
});
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
if (props.onChange) {
|
|
props.onChange();
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`However, 'props' will change when *any* prop changes, so the ` +
|
|
`preferred fix is to destructure the 'props' object outside ` +
|
|
`of the useEffect call and refer to those specific ` +
|
|
`props inside useEffect.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
if (props.onChange) {
|
|
props.onChange();
|
|
}
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
if (props?.onChange) {
|
|
props?.onChange();
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`However, 'props' will change when *any* prop changes, so the ` +
|
|
`preferred fix is to destructure the 'props' object outside ` +
|
|
`of the useEffect call and refer to those specific ` +
|
|
`props inside useEffect.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
if (props?.onChange) {
|
|
props?.onChange();
|
|
}
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
function play() {
|
|
props.onPlay();
|
|
}
|
|
function pause() {
|
|
props.onPause();
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`However, 'props' will change when *any* prop changes, so the ` +
|
|
`preferred fix is to destructure the 'props' object outside ` +
|
|
`of the useEffect call and refer to those specific ` +
|
|
`props inside useEffect.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
function play() {
|
|
props.onPlay();
|
|
}
|
|
function pause() {
|
|
props.onPause();
|
|
}
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
if (props.foo.onChange) {
|
|
props.foo.onChange();
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect 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) {
|
|
useEffect(() => {
|
|
if (props.foo.onChange) {
|
|
props.foo.onChange();
|
|
}
|
|
}, [props.foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
props.onChange();
|
|
if (props.foo.onChange) {
|
|
props.foo.onChange();
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`However, 'props' will change when *any* prop changes, so the ` +
|
|
`preferred fix is to destructure the 'props' object outside ` +
|
|
`of the useEffect call and refer to those specific ` +
|
|
`props inside useEffect.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
props.onChange();
|
|
if (props.foo.onChange) {
|
|
props.foo.onChange();
|
|
}
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const [skillsCount] = useState();
|
|
useEffect(() => {
|
|
if (skillsCount === 0 && !props.isEditMode) {
|
|
props.toggleEditMode();
|
|
}
|
|
}, [skillsCount, props.isEditMode, props.toggleEditMode]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`However, 'props' will change when *any* prop changes, so the ` +
|
|
`preferred fix is to destructure the 'props' object outside ` +
|
|
`of the useEffect call and refer to those specific ` +
|
|
`props inside useEffect.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [skillsCount, props.isEditMode, props.toggleEditMode, props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const [skillsCount] = useState();
|
|
useEffect(() => {
|
|
if (skillsCount === 0 && !props.isEditMode) {
|
|
props.toggleEditMode();
|
|
}
|
|
}, [skillsCount, props.isEditMode, props.toggleEditMode, props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const [skillsCount] = useState();
|
|
useEffect(() => {
|
|
if (skillsCount === 0 && !props.isEditMode) {
|
|
props.toggleEditMode();
|
|
}
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'props' and 'skillsCount'. " +
|
|
'Either include them or remove the dependency array. ' +
|
|
`However, 'props' will change when *any* prop changes, so the ` +
|
|
`preferred fix is to destructure the 'props' object outside ` +
|
|
`of the useEffect call and refer to those specific ` +
|
|
`props inside useEffect.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props, skillsCount]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const [skillsCount] = useState();
|
|
useEffect(() => {
|
|
if (skillsCount === 0 && !props.isEditMode) {
|
|
props.toggleEditMode();
|
|
}
|
|
}, [props, skillsCount]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
externalCall(props);
|
|
props.onChange();
|
|
}, []);
|
|
}
|
|
`,
|
|
// Don't suggest to destructure props here since you can't.
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
externalCall(props);
|
|
props.onChange();
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
props.onChange();
|
|
externalCall(props);
|
|
}, []);
|
|
}
|
|
`,
|
|
// Don't suggest to destructure props here since you can't.
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
useEffect(() => {
|
|
props.onChange();
|
|
externalCall(props);
|
|
}, [props]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let value;
|
|
let value2;
|
|
let value3;
|
|
let value4;
|
|
let asyncValue;
|
|
useEffect(() => {
|
|
if (value4) {
|
|
value = {};
|
|
}
|
|
value2 = 100;
|
|
value = 43;
|
|
value4 = true;
|
|
console.log(value2);
|
|
console.log(value3);
|
|
setTimeout(() => {
|
|
asyncValue = 100;
|
|
});
|
|
}, []);
|
|
}
|
|
`,
|
|
// This is a separate warning unrelated to others.
|
|
// We could've made a separate rule for it but it's rare enough to name it.
|
|
// No suggestions because the intent isn't clear.
|
|
errors: [
|
|
{
|
|
message:
|
|
// value2
|
|
`Assignments to the 'value2' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
// value
|
|
`Assignments to the 'value' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
// value4
|
|
`Assignments to the 'value4' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
// asyncValue
|
|
`Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let value;
|
|
let value2;
|
|
let value3;
|
|
let asyncValue;
|
|
useEffect(() => {
|
|
value = {};
|
|
value2 = 100;
|
|
value = 43;
|
|
console.log(value2);
|
|
console.log(value3);
|
|
setTimeout(() => {
|
|
asyncValue = 100;
|
|
});
|
|
}, [value, value2, value3]);
|
|
}
|
|
`,
|
|
// This is a separate warning unrelated to others.
|
|
// We could've made a separate rule for it but it's rare enough to name it.
|
|
// No suggestions because the intent isn't clear.
|
|
errors: [
|
|
{
|
|
message:
|
|
// value
|
|
`Assignments to the 'value' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
// value2
|
|
`Assignments to the 'value2' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
// asyncValue
|
|
`Assignments to the 'asyncValue' variable from inside React Hook useEffect ` +
|
|
`will be lost after each render. To preserve the value over time, ` +
|
|
`store it in a useRef Hook and keep the mutable value in the '.current' property. ` +
|
|
`Otherwise, you can move this variable directly inside useEffect.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMove);
|
|
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
|
}, []);
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef?.current?.addEventListener('mousemove', handleMove);
|
|
return () => myRef?.current?.removeEventListener('mousemove', handleMove);
|
|
}, []);
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMove);
|
|
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
|
});
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useMyThing(myRef) {
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMove);
|
|
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
|
}, [myRef]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useMyThing(myRef) {
|
|
useEffect(() => {
|
|
const handleMouse = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMouse);
|
|
myRef.current.addEventListener('mousein', handleMouse);
|
|
return function() {
|
|
setTimeout(() => {
|
|
myRef.current.removeEventListener('mousemove', handleMouse);
|
|
myRef.current.removeEventListener('mousein', handleMouse);
|
|
});
|
|
}
|
|
}, [myRef]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function useMyThing(myRef, active) {
|
|
useEffect(() => {
|
|
const handleMove = () => {};
|
|
if (active) {
|
|
myRef.current.addEventListener('mousemove', handleMove);
|
|
return function() {
|
|
setTimeout(() => {
|
|
myRef.current.removeEventListener('mousemove', handleMove);
|
|
});
|
|
}
|
|
}
|
|
}, [myRef, active]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: `
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useLayoutEffect_SAFE_FOR_SSR(() => {
|
|
const handleMove = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMove);
|
|
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
|
});
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
output: `
|
|
function MyComponent() {
|
|
const myRef = useRef();
|
|
useLayoutEffect_SAFE_FOR_SSR(() => {
|
|
const handleMove = () => {};
|
|
myRef.current.addEventListener('mousemove', handleMove);
|
|
return () => myRef.current.removeEventListener('mousemove', handleMove);
|
|
});
|
|
return <div ref={myRef} />;
|
|
}
|
|
`,
|
|
errors: [
|
|
`The ref value 'myRef.current' will likely have changed by the time ` +
|
|
`this effect cleanup function runs. If this ref points to a node ` +
|
|
`rendered by React, copy 'myRef.current' to a variable inside the effect, ` +
|
|
`and use that variable in the cleanup function.`,
|
|
],
|
|
options: [{additionalHooks: 'useLayoutEffect_SAFE_FOR_SSR'}],
|
|
},
|
|
{
|
|
// Autofix ignores constant primitives (leaving the ones that are there).
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = 42;
|
|
const local2 = '42';
|
|
const local3 = null;
|
|
const local4 = {};
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
console.log(local3);
|
|
console.log(local4);
|
|
}, [local1, local3]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local4'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local1, local3, local4]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local1 = 42;
|
|
const local2 = '42';
|
|
const local3 = null;
|
|
const local4 = {};
|
|
useEffect(() => {
|
|
console.log(local1);
|
|
console.log(local2);
|
|
console.log(local3);
|
|
console.log(local4);
|
|
}, [local1, local3, local4]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
window.scrollTo(0, 0);
|
|
}, [window]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has an unnecessary dependency: 'window'. " +
|
|
'Either exclude it or remove the dependency array. ' +
|
|
"Outer scope values like 'window' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
window.scrollTo(0, 0);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
console.log(MutableStore.hello);
|
|
}, [MutableStore.hello]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has an unnecessary dependency: 'MutableStore.hello'. " +
|
|
'Either exclude it or remove the dependency array. ' +
|
|
"Outer scope values like 'MutableStore.hello' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
|
|
function MyComponent() {
|
|
useEffect(() => {
|
|
console.log(MutableStore.hello);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
useEffect(() => {
|
|
console.log(MutableStore.hello.world, props.foo, x, y, z, global.stuff);
|
|
}, [MutableStore.hello.world, props.foo, x, y, z, global.stuff]);
|
|
}
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has unnecessary dependencies: ' +
|
|
"'MutableStore.hello.world', 'global.stuff', and 'z'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo, x, y]',
|
|
output: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
useEffect(() => {
|
|
console.log(MutableStore.hello.world, props.foo, x, y, z, global.stuff);
|
|
}, [props.foo, x, y]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
useEffect(() => {
|
|
// nothing
|
|
}, [MutableStore.hello.world, props.foo, x, y, z, global.stuff]);
|
|
}
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect has unnecessary dependencies: ' +
|
|
"'MutableStore.hello.world', 'global.stuff', and 'z'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
// The output should contain the ones that are inside a component
|
|
// since there are legit reasons to over-specify them for effects.
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.foo, x, y]',
|
|
output: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
useEffect(() => {
|
|
// nothing
|
|
}, [props.foo, x, y]);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
const fn = useCallback(() => {
|
|
// nothing
|
|
}, [MutableStore.hello.world, props.foo, x, y, z, global.stuff]);
|
|
}
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useCallback has unnecessary dependencies: ' +
|
|
"'MutableStore.hello.world', 'global.stuff', 'props.foo', 'x', 'y', and 'z'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
const fn = useCallback(() => {
|
|
// nothing
|
|
}, []);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
const fn = useCallback(() => {
|
|
// nothing
|
|
}, [MutableStore?.hello?.world, props.foo, x, y, z, global?.stuff]);
|
|
}
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useCallback has unnecessary dependencies: ' +
|
|
"'MutableStore.hello.world', 'global.stuff', 'props.foo', 'x', 'y', and 'z'. " +
|
|
'Either exclude them or remove the dependency array. ' +
|
|
"Outer scope values like 'MutableStore.hello.world' aren't valid dependencies " +
|
|
"because mutating them doesn't re-render the component.",
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
import MutableStore from 'store';
|
|
let z = {};
|
|
|
|
function MyComponent(props) {
|
|
let x = props.foo;
|
|
{
|
|
let y = props.bar;
|
|
const fn = useCallback(() => {
|
|
// nothing
|
|
}, []);
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Every almost-static function is tainted by a dynamic value.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
setTimeout(() => console.log(taint));
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'handleNext1'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext1]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
setTimeout(() => console.log(taint));
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useLayoutEffect has a missing dependency: 'handleNext2'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext2]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
setTimeout(() => console.log(taint));
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useMemo has a missing dependency: 'handleNext3'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext3]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
setTimeout(() => console.log(taint));
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, [handleNext3]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
function handleChange() {}
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'handleNext1'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext1]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
function handleChange() {}
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useLayoutEffect has a missing dependency: 'handleNext2'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext2]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
function handleChange() {}
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useMemo has a missing dependency: 'handleNext3'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext3]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
function handleChange() {}
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, [handleNext3]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regression test
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
const handleChange = () => {};
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'handleNext1'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext1]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
const handleChange = () => {};
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useLayoutEffect has a missing dependency: 'handleNext2'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext2]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
const handleChange = () => {};
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"React Hook useMemo has a missing dependency: 'handleNext3'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [handleNext3]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let [, dispatch] = React.useReducer();
|
|
let taint = props.foo;
|
|
|
|
// Shouldn't affect anything
|
|
const handleChange = () => {};
|
|
|
|
function handleNext1(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = (value) => {
|
|
setState(taint(value));
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function(value) {
|
|
console.log(taint);
|
|
dispatch({ type: 'x', value });
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, []);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, []);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, [handleNext3]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
|
|
function handleNext(value) {
|
|
setState(value);
|
|
}
|
|
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The 'handleNext' function makes the dependencies of ` +
|
|
`useEffect Hook (at line 11) change on every render. ` +
|
|
`Move it inside the useEffect callback. Alternatively, ` +
|
|
`wrap the definition of 'handleNext' in its own useCallback() Hook.`,
|
|
// Not gonna fix a function definition
|
|
// because it's not always safe due to hoisting.
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Even if the function only references static values,
|
|
// once you specify it in deps, it will invalidate them.
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
|
|
const handleNext = (value) => {
|
|
setState(value);
|
|
};
|
|
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The 'handleNext' function makes the dependencies of ` +
|
|
`useEffect Hook (at line 11) change on every render. ` +
|
|
`Move it inside the useEffect callback. Alternatively, ` +
|
|
`wrap the definition of 'handleNext' in its own useCallback() Hook.`,
|
|
// We don't fix moving (too invasive). But that's the suggested fix
|
|
// when only effect uses this function. Otherwise, we'd useCallback.
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Even if the function only references static values,
|
|
// once you specify it in deps, it will invalidate them.
|
|
// However, we can't suggest moving handleNext into the
|
|
// effect because it is *also* used outside of it.
|
|
// So our suggestion is useCallback().
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
|
|
const handleNext = (value) => {
|
|
setState(value);
|
|
};
|
|
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
|
|
return <div onClick={handleNext} />;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The 'handleNext' function makes the dependencies of ` +
|
|
`useEffect Hook (at line 11) change on every render. ` +
|
|
`To fix this, wrap the definition of 'handleNext' in its own useCallback() Hook.`,
|
|
// We fix this one with useCallback since it's
|
|
// the easy fix and you can't just move it into effect.
|
|
suggestions: [
|
|
{
|
|
desc: "Wrap the definition of 'handleNext' in its own useCallback() Hook.",
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
|
|
const handleNext = useCallback((value) => {
|
|
setState(value);
|
|
});
|
|
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
|
|
return <div onClick={handleNext} />;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext1() {
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = () => {
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function() {
|
|
console.log('hello');
|
|
};
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
return Store.subscribe(handleNext3);
|
|
}, [handleNext3]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
|
|
'(at line 14) change on every render. Move it inside the useEffect callback. ' +
|
|
"Alternatively, wrap the definition of 'handleNext1' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
|
|
'(at line 17) change on every render. Move it inside the useLayoutEffect callback. ' +
|
|
"Alternatively, wrap the definition of 'handleNext2' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext3' function makes the dependencies of useMemo Hook " +
|
|
'(at line 20) change on every render. Move it inside the useMemo callback. ' +
|
|
"Alternatively, wrap the definition of 'handleNext3' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext1() {
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = () => {
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function() {
|
|
console.log('hello');
|
|
};
|
|
useEffect(() => {
|
|
handleNext1();
|
|
return Store.subscribe(() => handleNext1());
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
handleNext2();
|
|
return Store.subscribe(() => handleNext2());
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
handleNext3();
|
|
return Store.subscribe(() => handleNext3());
|
|
}, [handleNext3]);
|
|
}
|
|
`,
|
|
// Suggestions don't wrap into useCallback here
|
|
// because they are only referenced by effect itself.
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
|
|
'(at line 15) change on every render. Move it inside the useEffect callback. ' +
|
|
"Alternatively, wrap the definition of 'handleNext1' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
|
|
'(at line 19) change on every render. Move it inside the useLayoutEffect callback. ' +
|
|
"Alternatively, wrap the definition of 'handleNext2' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext3' function makes the dependencies of useMemo Hook " +
|
|
'(at line 23) change on every render. Move it inside the useMemo callback. ' +
|
|
"Alternatively, wrap the definition of 'handleNext3' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext1() {
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = () => {
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = function() {
|
|
console.log('hello');
|
|
};
|
|
useEffect(() => {
|
|
handleNext1();
|
|
return Store.subscribe(() => handleNext1());
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
handleNext2();
|
|
return Store.subscribe(() => handleNext2());
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
handleNext3();
|
|
return Store.subscribe(() => handleNext3());
|
|
}, [handleNext3]);
|
|
return (
|
|
<div
|
|
onClick={() => {
|
|
handleNext1();
|
|
setTimeout(handleNext2);
|
|
setTimeout(() => {
|
|
handleNext3();
|
|
});
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
|
|
'(at line 15) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext1' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext2' function makes the dependencies of useLayoutEffect Hook " +
|
|
'(at line 19) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext2' in its own useCallback() Hook.",
|
|
// Suggestion wraps into useCallback where possible (variables only)
|
|
// because they are only referenced outside the effect.
|
|
suggestions: [
|
|
{
|
|
desc: "Wrap the definition of 'handleNext2' in its own useCallback() Hook.",
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext1() {
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = useCallback(() => {
|
|
console.log('hello');
|
|
});
|
|
let handleNext3 = function() {
|
|
console.log('hello');
|
|
};
|
|
useEffect(() => {
|
|
handleNext1();
|
|
return Store.subscribe(() => handleNext1());
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
handleNext2();
|
|
return Store.subscribe(() => handleNext2());
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
handleNext3();
|
|
return Store.subscribe(() => handleNext3());
|
|
}, [handleNext3]);
|
|
return (
|
|
<div
|
|
onClick={() => {
|
|
handleNext1();
|
|
setTimeout(handleNext2);
|
|
setTimeout(() => {
|
|
handleNext3();
|
|
});
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext3' function makes the dependencies of useMemo Hook " +
|
|
'(at line 23) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext3' in its own useCallback() Hook.",
|
|
// Autofix wraps into useCallback where possible (variables only)
|
|
// because they are only referenced outside the effect.
|
|
suggestions: [
|
|
{
|
|
desc: "Wrap the definition of 'handleNext3' in its own useCallback() Hook.",
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
function handleNext1() {
|
|
console.log('hello');
|
|
}
|
|
const handleNext2 = () => {
|
|
console.log('hello');
|
|
};
|
|
let handleNext3 = useCallback(function() {
|
|
console.log('hello');
|
|
});
|
|
useEffect(() => {
|
|
handleNext1();
|
|
return Store.subscribe(() => handleNext1());
|
|
}, [handleNext1]);
|
|
useLayoutEffect(() => {
|
|
handleNext2();
|
|
return Store.subscribe(() => handleNext2());
|
|
}, [handleNext2]);
|
|
useMemo(() => {
|
|
handleNext3();
|
|
return Store.subscribe(() => handleNext3());
|
|
}, [handleNext3]);
|
|
return (
|
|
<div
|
|
onClick={() => {
|
|
handleNext1();
|
|
setTimeout(handleNext2);
|
|
setTimeout(() => {
|
|
handleNext3();
|
|
});
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const handleNext1 = () => {
|
|
console.log('hello');
|
|
};
|
|
function handleNext2() {
|
|
console.log('hello');
|
|
}
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext1, handleNext2]);
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext1, handleNext2]);
|
|
}
|
|
`,
|
|
// Normally we'd suggest moving handleNext inside an
|
|
// effect. But it's used by more than one. So we
|
|
// suggest useCallback() and use it for the autofix
|
|
// where possible (variable but not declaration).
|
|
// TODO: we could coalesce messages for the same function if it affects multiple Hooks.
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
|
|
'(at line 12) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext1' in its own useCallback() Hook.",
|
|
suggestions: [
|
|
{
|
|
desc: "Wrap the definition of 'handleNext1' in its own useCallback() Hook.",
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const handleNext1 = useCallback(() => {
|
|
console.log('hello');
|
|
});
|
|
function handleNext2() {
|
|
console.log('hello');
|
|
}
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext1, handleNext2]);
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext1, handleNext2]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext1' function makes the dependencies of useEffect Hook " +
|
|
'(at line 16) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext1' in its own useCallback() Hook.",
|
|
suggestions: [
|
|
{
|
|
desc: "Wrap the definition of 'handleNext1' in its own useCallback() Hook.",
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
const handleNext1 = useCallback(() => {
|
|
console.log('hello');
|
|
});
|
|
function handleNext2() {
|
|
console.log('hello');
|
|
}
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext1, handleNext2]);
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext1);
|
|
return Store.subscribe(handleNext2);
|
|
}, [handleNext1, handleNext2]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext2' function makes the dependencies of useEffect Hook " +
|
|
'(at line 12) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext2' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'handleNext2' function makes the dependencies of useEffect Hook " +
|
|
'(at line 16) change on every render. To fix this, wrap the ' +
|
|
"definition of 'handleNext2' in its own useCallback() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let handleNext = () => {
|
|
console.log('hello');
|
|
};
|
|
if (props.foo) {
|
|
handleNext = () => {
|
|
console.log('hello');
|
|
};
|
|
}
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'handleNext' function makes the dependencies of useEffect Hook " +
|
|
'(at line 13) change on every render. To fix this, wrap the definition of ' +
|
|
"'handleNext' in its own useCallback() Hook.",
|
|
// Normally we'd suggest moving handleNext inside an
|
|
// effect. But it's used more than once.
|
|
// TODO: our autofix here isn't quite sufficient because
|
|
// it only wraps the first definition. But seems ok.
|
|
suggestions: [
|
|
{
|
|
desc: "Wrap the definition of 'handleNext' in its own useCallback() Hook.",
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let handleNext = useCallback(() => {
|
|
console.log('hello');
|
|
});
|
|
if (props.foo) {
|
|
handleNext = () => {
|
|
console.log('hello');
|
|
};
|
|
}
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let [, setState] = useState();
|
|
let taint = props.foo;
|
|
|
|
function handleNext(value) {
|
|
let value2 = value * taint;
|
|
setState(value2);
|
|
console.log('hello');
|
|
}
|
|
|
|
useEffect(() => {
|
|
return Store.subscribe(handleNext);
|
|
}, [handleNext]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The 'handleNext' function makes the dependencies of ` +
|
|
`useEffect Hook (at line 14) change on every render. ` +
|
|
`Move it inside the useEffect callback. Alternatively, wrap the ` +
|
|
`definition of 'handleNext' in its own useCallback() Hook.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count + 1);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'count'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`You can also do a functional update 'setCount(c => ...)' if you ` +
|
|
`only need 'count' in the 'setCount' call.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [count]',
|
|
output: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count + 1);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [count]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
let [increment, setIncrement] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'count' and 'increment'. " +
|
|
'Either include them or remove the dependency array. ' +
|
|
`You can also do a functional update 'setCount(c => ...)' if you ` +
|
|
`only need 'count' in the 'setCount' call.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [count, increment]',
|
|
output: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
let [increment, setIncrement] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [count, increment]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
let [increment, setIncrement] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'increment'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`You can also replace multiple useState variables with useReducer ` +
|
|
`if 'setCount' needs the current value of 'increment'.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [increment]',
|
|
output: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
let [increment, setIncrement] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [increment]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
let increment = useCustomHook();
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
// This intentionally doesn't show the reducer message
|
|
// because we don't know if it's safe for it to close over a value.
|
|
// We only show it for state variables (and possibly props).
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'increment'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [increment]',
|
|
output: normalizeIndent`
|
|
function Counter() {
|
|
let [count, setCount] = useState(0);
|
|
let increment = useCustomHook();
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [increment]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter({ step }) {
|
|
let [count, setCount] = useState(0);
|
|
|
|
function increment(x) {
|
|
return x + step;
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => increment(count));
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
// This intentionally doesn't show the reducer message
|
|
// because we don't know if it's safe for it to close over a value.
|
|
// We only show it for state variables (and possibly props).
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'increment'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [increment]',
|
|
output: normalizeIndent`
|
|
function Counter({ step }) {
|
|
let [count, setCount] = useState(0);
|
|
|
|
function increment(x) {
|
|
return x + step;
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => increment(count));
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [increment]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter({ step }) {
|
|
let [count, setCount] = useState(0);
|
|
|
|
function increment(x) {
|
|
return x + step;
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => increment(count));
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [increment]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`The 'increment' function makes the dependencies of useEffect Hook ` +
|
|
`(at line 14) change on every render. Move it inside the useEffect callback. ` +
|
|
`Alternatively, wrap the definition of \'increment\' in its own ` +
|
|
`useCallback() Hook.`,
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter({ increment }) {
|
|
let [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'increment'. " +
|
|
'Either include it or remove the dependency array. ' +
|
|
`If 'setCount' needs the current value of 'increment', ` +
|
|
`you can also switch to useReducer instead of useState and read 'increment' in the reducer.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [increment]',
|
|
output: normalizeIndent`
|
|
function Counter({ increment }) {
|
|
let [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
setCount(count => count + increment);
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [increment]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Counter() {
|
|
const [count, setCount] = useState(0);
|
|
|
|
function tick() {
|
|
setCount(count + 1);
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
tick();
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
// TODO: ideally this should suggest useState updater form
|
|
// since this code doesn't actually work. The autofix could
|
|
// at least avoid suggesting 'tick' since it's obviously
|
|
// always different, and thus useless.
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'tick'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [tick]',
|
|
output: normalizeIndent`
|
|
function Counter() {
|
|
const [count, setCount] = useState(0);
|
|
|
|
function tick() {
|
|
setCount(count + 1);
|
|
}
|
|
|
|
useEffect(() => {
|
|
let id = setInterval(() => {
|
|
tick();
|
|
}, 1000);
|
|
return () => clearInterval(id);
|
|
}, [tick]);
|
|
|
|
return <h1>{count}</h1>;
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// Regression test for a crash
|
|
code: normalizeIndent`
|
|
function Podcasts() {
|
|
useEffect(() => {
|
|
alert(podcasts);
|
|
}, []);
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has a missing dependency: 'podcasts'. ` +
|
|
`Either include it or remove the dependency array.`,
|
|
// Note: this autofix is shady because
|
|
// the variable is used before declaration.
|
|
// TODO: Maybe we can catch those fixes and not autofix.
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [podcasts]',
|
|
output: normalizeIndent`
|
|
function Podcasts() {
|
|
useEffect(() => {
|
|
alert(podcasts);
|
|
}, [podcasts]);
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
|
|
`Either include it or remove the dependency array. ` +
|
|
`If 'fetchPodcasts' changes too often, ` +
|
|
`find the parent component that defines it and wrap that definition in useCallback.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [fetchPodcasts, id]',
|
|
output: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [fetchPodcasts, id]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Podcasts({ api: { fetchPodcasts }, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
|
|
`Either include it or remove the dependency array. ` +
|
|
`If 'fetchPodcasts' changes too often, ` +
|
|
`find the parent component that defines it and wrap that definition in useCallback.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [fetchPodcasts, id]',
|
|
output: normalizeIndent`
|
|
function Podcasts({ api: { fetchPodcasts }, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [fetchPodcasts, id]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, fetchPodcasts2, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
setTimeout(() => {
|
|
console.log(id);
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
fetchPodcasts2(id).then(setPodcasts);
|
|
});
|
|
}, [id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has missing dependencies: 'fetchPodcasts' and 'fetchPodcasts2'. ` +
|
|
`Either include them or remove the dependency array. ` +
|
|
`If 'fetchPodcasts' changes too often, ` +
|
|
`find the parent component that defines it and wrap that definition in useCallback.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [fetchPodcasts, fetchPodcasts2, id]',
|
|
output: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, fetchPodcasts2, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
setTimeout(() => {
|
|
console.log(id);
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
fetchPodcasts2(id).then(setPodcasts);
|
|
});
|
|
}, [fetchPodcasts, fetchPodcasts2, id]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
console.log(fetchPodcasts);
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
|
|
`Either include it or remove the dependency array. ` +
|
|
`If 'fetchPodcasts' changes too often, ` +
|
|
`find the parent component that defines it and wrap that definition in useCallback.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [fetchPodcasts, id]',
|
|
output: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
console.log(fetchPodcasts);
|
|
fetchPodcasts(id).then(setPodcasts);
|
|
}, [fetchPodcasts, id]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
console.log(fetchPodcasts);
|
|
fetchPodcasts?.(id).then(setPodcasts);
|
|
}, [id]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has a missing dependency: 'fetchPodcasts'. ` +
|
|
`Either include it or remove the dependency array. ` +
|
|
`If 'fetchPodcasts' changes too often, ` +
|
|
`find the parent component that defines it and wrap that definition in useCallback.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [fetchPodcasts, id]',
|
|
output: normalizeIndent`
|
|
function Podcasts({ fetchPodcasts, id }) {
|
|
let [podcasts, setPodcasts] = useState(null);
|
|
useEffect(() => {
|
|
console.log(fetchPodcasts);
|
|
fetchPodcasts?.(id).then(setPodcasts);
|
|
}, [fetchPodcasts, id]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
// The mistake here is that it was moved inside the effect
|
|
// so it can't be referenced in the deps array.
|
|
code: normalizeIndent`
|
|
function Thing() {
|
|
useEffect(() => {
|
|
const fetchData = async () => {};
|
|
fetchData();
|
|
}, [fetchData]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect has an unnecessary dependency: 'fetchData'. ` +
|
|
`Either exclude it or remove the dependency array.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function Thing() {
|
|
useEffect(() => {
|
|
const fetchData = async () => {};
|
|
fetchData();
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Hello() {
|
|
const [state, setState] = useState(0);
|
|
useEffect(() => {
|
|
setState({});
|
|
});
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect contains a call to 'setState'. ` +
|
|
`Without a list of dependencies, this can lead to an infinite chain of updates. ` +
|
|
`To fix this, pass [] as a second argument to the useEffect Hook.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Add dependencies array: []',
|
|
output: normalizeIndent`
|
|
function Hello() {
|
|
const [state, setState] = useState(0);
|
|
useEffect(() => {
|
|
setState({});
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Hello() {
|
|
const [data, setData] = useState(0);
|
|
useEffect(() => {
|
|
fetchData.then(setData);
|
|
});
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect contains a call to 'setData'. ` +
|
|
`Without a list of dependencies, this can lead to an infinite chain of updates. ` +
|
|
`To fix this, pass [] as a second argument to the useEffect Hook.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Add dependencies array: []',
|
|
output: normalizeIndent`
|
|
function Hello() {
|
|
const [data, setData] = useState(0);
|
|
useEffect(() => {
|
|
fetchData.then(setData);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Hello({ country }) {
|
|
const [data, setData] = useState(0);
|
|
useEffect(() => {
|
|
fetchData(country).then(setData);
|
|
});
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect contains a call to 'setData'. ` +
|
|
`Without a list of dependencies, this can lead to an infinite chain of updates. ` +
|
|
`To fix this, pass [country] as a second argument to the useEffect Hook.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Add dependencies array: [country]',
|
|
output: normalizeIndent`
|
|
function Hello({ country }) {
|
|
const [data, setData] = useState(0);
|
|
useEffect(() => {
|
|
fetchData(country).then(setData);
|
|
}, [country]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Hello({ prop1, prop2 }) {
|
|
const [state, setState] = useState(0);
|
|
useEffect(() => {
|
|
if (prop1) {
|
|
setState(prop2);
|
|
}
|
|
});
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`React Hook useEffect contains a call to 'setState'. ` +
|
|
`Without a list of dependencies, this can lead to an infinite chain of updates. ` +
|
|
`To fix this, pass [prop1, prop2] as a second argument to the useEffect Hook.`,
|
|
suggestions: [
|
|
{
|
|
desc: 'Add dependencies array: [prop1, prop2]',
|
|
output: normalizeIndent`
|
|
function Hello({ prop1, prop2 }) {
|
|
const [state, setState] = useState(0);
|
|
useEffect(() => {
|
|
if (prop1) {
|
|
setState(prop2);
|
|
}
|
|
}, [prop1, prop2]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Thing() {
|
|
useEffect(async () => {}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`Effect callbacks are synchronous to prevent race conditions. ` +
|
|
`Put the async function inside:\n\n` +
|
|
'useEffect(() => {\n' +
|
|
' async function fetchData() {\n' +
|
|
' // You can await here\n' +
|
|
' const response = await MyAPI.getData(someId);\n' +
|
|
' // ...\n' +
|
|
' }\n' +
|
|
' fetchData();\n' +
|
|
`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
|
|
'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Thing() {
|
|
useEffect(async () => {});
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
`Effect callbacks are synchronous to prevent race conditions. ` +
|
|
`Put the async function inside:\n\n` +
|
|
'useEffect(() => {\n' +
|
|
' async function fetchData() {\n' +
|
|
' // You can await here\n' +
|
|
' const response = await MyAPI.getData(someId);\n' +
|
|
' // ...\n' +
|
|
' }\n' +
|
|
' fetchData();\n' +
|
|
`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
|
|
'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Example() {
|
|
const foo = useCallback(() => {
|
|
foo();
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has an unnecessary dependency: 'foo'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function Example() {
|
|
const foo = useCallback(() => {
|
|
foo();
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Example({ prop }) {
|
|
const foo = useCallback(() => {
|
|
prop.hello(foo);
|
|
}, [foo]);
|
|
const bar = useCallback(() => {
|
|
foo();
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useCallback has a missing dependency: 'prop'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [prop]',
|
|
output: normalizeIndent`
|
|
function Example({ prop }) {
|
|
const foo = useCallback(() => {
|
|
prop.hello(foo);
|
|
}, [prop]);
|
|
const bar = useCallback(() => {
|
|
foo();
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
function myEffect() {
|
|
console.log(local);
|
|
}
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
function myEffect() {
|
|
console.log(local);
|
|
}
|
|
useEffect(myEffect, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = () => {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = () => {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = function() {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = function() {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = () => {
|
|
otherThing();
|
|
};
|
|
const otherThing = () => {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'otherThing'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [otherThing]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = () => {
|
|
otherThing();
|
|
};
|
|
const otherThing = () => {
|
|
console.log(local);
|
|
};
|
|
useEffect(myEffect, [otherThing]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = debounce(() => {
|
|
console.log(local);
|
|
}, delay);
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'myEffect'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [myEffect]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = debounce(() => {
|
|
console.log(local);
|
|
}, delay);
|
|
useEffect(myEffect, [myEffect]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = debounce(() => {
|
|
console.log(local);
|
|
}, delay);
|
|
useEffect(myEffect, [local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'myEffect'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [myEffect]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
const myEffect = debounce(() => {
|
|
console.log(local);
|
|
}, delay);
|
|
useEffect(myEffect, [myEffect]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({myEffect}) {
|
|
useEffect(myEffect, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'myEffect'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [myEffect]',
|
|
output: normalizeIndent`
|
|
function MyComponent({myEffect}) {
|
|
useEffect(myEffect, [myEffect]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(debounce(() => {
|
|
console.log(local);
|
|
}, delay), []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'React Hook useEffect received a function whose dependencies ' +
|
|
'are unknown. Pass an inline function instead.',
|
|
suggestions: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
// Dangerous autofix is enabled due to the option:
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {};
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
},
|
|
],
|
|
// Keep this until major IDEs and VS Code FB ESLint plugin support Suggestions API.
|
|
options: [{enableDangerousAutofixThisMayCauseInfiniteLoops: true}],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let foo = {}
|
|
useEffect(() => {
|
|
foo.bar.baz = 43;
|
|
props.foo.bar.baz = 1;
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'foo.bar' and 'props.foo.bar'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [foo.bar, props.foo.bar]',
|
|
output: normalizeIndent`
|
|
function MyComponent(props) {
|
|
let foo = {}
|
|
useEffect(() => {
|
|
foo.bar.baz = 43;
|
|
props.foo.bar.baz = 1;
|
|
}, [foo.bar, props.foo.bar]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = [];
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' array makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = () => {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' function makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the definition of 'foo' in its own " +
|
|
'useCallback() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = function bar(){};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' function makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the definition of 'foo' in its own " +
|
|
'useCallback() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = class {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' class makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = true ? {} : "fine";
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' conditional could make the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = bar || {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' logical expression could make the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = bar ?? {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' logical expression could make the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = bar && {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' logical expression could make the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = bar ? baz ? {} : null : null;
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' conditional could make the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
let foo = {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
var foo = {};
|
|
useMemo(() => foo, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useMemo Hook (at line 4) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = {};
|
|
useCallback(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useCallback Hook (at line 6) change on every render. " +
|
|
"Move it inside the useCallback callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = {};
|
|
useEffect(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useEffect Hook (at line 6) change on every render. " +
|
|
"Move it inside the useEffect callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = {};
|
|
useLayoutEffect(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useLayoutEffect Hook (at line 6) change on every render. " +
|
|
"Move it inside the useLayoutEffect callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Component() {
|
|
const foo = {};
|
|
useImperativeHandle(
|
|
ref,
|
|
() => {
|
|
console.log(foo);
|
|
},
|
|
[foo]
|
|
);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useImperativeHandle Hook (at line 9) change on every render. " +
|
|
"Move it inside the useImperativeHandle callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo(section) {
|
|
const foo = section.section_components?.edges ?? [];
|
|
useEffect(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' logical expression could make the dependencies of useEffect Hook (at line 6) change on every render. " +
|
|
"Move it inside the useEffect callback. Alternatively, wrap the initialization of 'foo' in its own " +
|
|
'useMemo() Hook.',
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo(section) {
|
|
const foo = {};
|
|
console.log(foo);
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useMemo Hook (at line 7) change on every render. " +
|
|
"To fix this, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = <>Hi!</>;
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' JSX fragment makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = <div>Hi!</div>;
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' JSX element makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = bar = {};
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' assignment expression makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = new String('foo'); // Note 'foo' will be boxed, and thus an object and thus compared by reference.
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object construction makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = new Map([]);
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object construction makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = /reg/;
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' regular expression makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
class Bar {};
|
|
useMemo(() => {
|
|
console.log(new Bar());
|
|
}, [Bar]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'Bar' class makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'Bar' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = {};
|
|
useLayoutEffect(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
useEffect(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useLayoutEffect Hook (at line 6) change on every render. " +
|
|
"To fix this, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useEffect Hook (at line 9) change on every render. " +
|
|
"To fix this, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
if (__EXPERIMENTAL__) {
|
|
tests.valid = [
|
|
...tests.valid,
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ theme }) {
|
|
const onStuff = useEffectEvent(() => {
|
|
showNotification(theme);
|
|
});
|
|
useEffect(() => {
|
|
onStuff();
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
];
|
|
|
|
tests.invalid = [
|
|
...tests.invalid,
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent({ theme }) {
|
|
const onStuff = useEffectEvent(() => {
|
|
showNotification(theme);
|
|
});
|
|
useEffect(() => {
|
|
onStuff();
|
|
}, [onStuff]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
'Functions returned from `useEffectEvent` must not be included in the dependency array. ' +
|
|
'Remove `onStuff` from the list.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Remove the dependency `onStuff`',
|
|
output: normalizeIndent`
|
|
function MyComponent({ theme }) {
|
|
const onStuff = useEffectEvent(() => {
|
|
showNotification(theme);
|
|
});
|
|
useEffect(() => {
|
|
onStuff();
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
// Tests that are only valid/invalid across parsers supporting Flow
|
|
const testsFlow = {
|
|
valid: [
|
|
// Ignore Generic Type Variables for arrow functions
|
|
{
|
|
code: normalizeIndent`
|
|
function Example({ prop }) {
|
|
const bar = useEffect(<T>(a: T): Hello => {
|
|
prop();
|
|
}, [prop]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
invalid: [
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = ({}: any);
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
// Tests that are only valid/invalid across parsers supporting TypeScript
|
|
const testsTypescript = {
|
|
valid: [
|
|
{
|
|
// `ref` is still constant, despite the cast.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
|
|
useEffect(() => {
|
|
console.log(ref.current);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const [state, setState] = React.useState<number>(0);
|
|
|
|
useEffect(() => {
|
|
const someNumber: typeof state = 2;
|
|
setState(prevState => prevState + someNumber);
|
|
}, [])
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function App() {
|
|
const foo = {x: 1};
|
|
React.useEffect(() => {
|
|
const bar = {x: 2};
|
|
const baz = bar as typeof foo;
|
|
console.log(baz);
|
|
}, []);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
invalid: [
|
|
{
|
|
// `local` is still non-constant, despite the cast.
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {} as string;
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'local'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [local]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const local = {} as string;
|
|
useEffect(() => {
|
|
console.log(local);
|
|
}, [local]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function App() {
|
|
const foo = {x: 1};
|
|
const bar = {x: 2};
|
|
useEffect(() => {
|
|
const baz = bar as typeof foo;
|
|
console.log(baz);
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'bar'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [bar]',
|
|
output: normalizeIndent`
|
|
function App() {
|
|
const foo = {x: 1};
|
|
const bar = {x: 2};
|
|
useEffect(() => {
|
|
const baz = bar as typeof foo;
|
|
console.log(baz);
|
|
}, [bar]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza.crust,
|
|
toppings: pizza?.toppings,
|
|
}), []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has missing dependencies: 'pizza.crust' and 'pizza?.toppings'. " +
|
|
'Either include them or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [pizza.crust, pizza?.toppings]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza.crust,
|
|
toppings: pizza?.toppings,
|
|
}), [pizza.crust, pizza?.toppings]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza?.crust,
|
|
density: pizza.crust.density,
|
|
}), []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'pizza.crust'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [pizza.crust]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza?.crust,
|
|
density: pizza.crust.density,
|
|
}), [pizza.crust]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza.crust,
|
|
density: pizza?.crust.density,
|
|
}), []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'pizza.crust'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [pizza.crust]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza.crust,
|
|
density: pizza?.crust.density,
|
|
}), [pizza.crust]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza?.crust,
|
|
density: pizza?.crust.density,
|
|
}), []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'pizza?.crust'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [pizza?.crust]',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const pizza = {};
|
|
|
|
useEffect(() => ({
|
|
crust: pizza?.crust,
|
|
density: pizza?.crust.density,
|
|
}), [pizza?.crust]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
// Regression test.
|
|
{
|
|
code: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props.upperViewHeight;
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props.upperViewHeight'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props.upperViewHeight]',
|
|
output: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props.upperViewHeight;
|
|
}, [props.upperViewHeight]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
// Regression test.
|
|
{
|
|
code: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props?.upperViewHeight;
|
|
}, []);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect has a missing dependency: 'props?.upperViewHeight'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [props?.upperViewHeight]',
|
|
output: normalizeIndent`
|
|
function Example(props) {
|
|
useEffect(() => {
|
|
let topHeight = 0;
|
|
topHeight = props?.upperViewHeight;
|
|
}, [props?.upperViewHeight]);
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function MyComponent() {
|
|
const [state, setState] = React.useState<number>(0);
|
|
|
|
useEffect(() => {
|
|
const someNumber: typeof state = 2;
|
|
setState(prevState => prevState + someNumber + state);
|
|
}, [])
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useEffect 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);
|
|
|
|
useEffect(() => {
|
|
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);
|
|
}, [state])
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook useMemo has an unnecessary dependency: 'state'. " +
|
|
'Either exclude it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: []',
|
|
output: normalizeIndent`
|
|
function MyComponent() {
|
|
const [state, setState] = React.useState<number>(0);
|
|
|
|
useMemo(() => {
|
|
const someNumber: typeof state = 2;
|
|
console.log(someNumber);
|
|
}, [])
|
|
}
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo() {
|
|
const foo = {} as any;
|
|
useMemo(() => {
|
|
console.log(foo);
|
|
}, [foo]);
|
|
}
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"The 'foo' object makes the dependencies of useMemo Hook (at line 6) change on every render. " +
|
|
"Move it inside the useMemo callback. Alternatively, wrap the initialization of 'foo' in its own useMemo() Hook.",
|
|
suggestions: undefined,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
// Tests that are only valid/invalid for `@typescript-eslint/parser@4.x`
|
|
const testsTypescriptEslintParserV4 = {
|
|
valid: [],
|
|
invalid: [
|
|
// TODO: Should also be invalid as part of the JS test suite i.e. be invalid with babel eslint parsers.
|
|
// It doesn't use any explicit types but any JS is still valid TS.
|
|
{
|
|
code: normalizeIndent`
|
|
function Foo({ Component }) {
|
|
React.useEffect(() => {
|
|
console.log(<Component />);
|
|
}, []);
|
|
};
|
|
`,
|
|
errors: [
|
|
{
|
|
message:
|
|
"React Hook React.useEffect has a missing dependency: 'Component'. " +
|
|
'Either include it or remove the dependency array.',
|
|
suggestions: [
|
|
{
|
|
desc: 'Update the dependencies array to be: [Component]',
|
|
output: normalizeIndent`
|
|
function Foo({ Component }) {
|
|
React.useEffect(() => {
|
|
console.log(<Component />);
|
|
}, [Component]);
|
|
};
|
|
`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
// For easier local testing
|
|
if (!process.env.CI) {
|
|
let only = [];
|
|
let skipped = [];
|
|
[
|
|
...tests.valid,
|
|
...tests.invalid,
|
|
...testsFlow.valid,
|
|
...testsFlow.invalid,
|
|
...testsTypescript.valid,
|
|
...testsTypescript.invalid,
|
|
...testsTypescriptEslintParserV4.valid,
|
|
...testsTypescriptEslintParserV4.invalid,
|
|
].forEach(t => {
|
|
if (t.skip) {
|
|
delete t.skip;
|
|
skipped.push(t);
|
|
}
|
|
if (t.only) {
|
|
delete t.only;
|
|
only.push(t);
|
|
}
|
|
});
|
|
const predicate = t => {
|
|
if (only.length > 0) {
|
|
return only.indexOf(t) !== -1;
|
|
}
|
|
if (skipped.length > 0) {
|
|
return skipped.indexOf(t) === -1;
|
|
}
|
|
return true;
|
|
};
|
|
tests.valid = tests.valid.filter(predicate);
|
|
tests.invalid = tests.invalid.filter(predicate);
|
|
testsFlow.valid = testsFlow.valid.filter(predicate);
|
|
testsFlow.invalid = testsFlow.invalid.filter(predicate);
|
|
testsTypescript.valid = testsTypescript.valid.filter(predicate);
|
|
testsTypescript.invalid = testsTypescript.invalid.filter(predicate);
|
|
}
|
|
|
|
describe('react-hooks', () => {
|
|
const parserOptions = {
|
|
ecmaFeatures: {
|
|
jsx: true,
|
|
},
|
|
ecmaVersion: 6,
|
|
sourceType: 'module',
|
|
};
|
|
|
|
const testsBabelEslint = {
|
|
valid: [...testsFlow.valid, ...tests.valid],
|
|
invalid: [...testsFlow.invalid, ...tests.invalid],
|
|
};
|
|
|
|
new ESLintTester({
|
|
parser: require.resolve('babel-eslint'),
|
|
parserOptions,
|
|
}).run('parser: babel-eslint', ReactHooksESLintRule, testsBabelEslint);
|
|
|
|
new ESLintTester({
|
|
parser: require.resolve('@babel/eslint-parser'),
|
|
parserOptions,
|
|
}).run(
|
|
'parser: @babel/eslint-parser',
|
|
ReactHooksESLintRule,
|
|
testsBabelEslint
|
|
);
|
|
|
|
const testsTypescriptEslintParser = {
|
|
valid: [...testsTypescript.valid, ...tests.valid],
|
|
invalid: [...testsTypescript.invalid, ...tests.invalid],
|
|
};
|
|
|
|
new ESLintTester({
|
|
parser: require.resolve('@typescript-eslint/parser-v2'),
|
|
parserOptions,
|
|
}).run(
|
|
'parser: @typescript-eslint/parser@2.x',
|
|
ReactHooksESLintRule,
|
|
testsTypescriptEslintParser
|
|
);
|
|
|
|
new ESLintTester({
|
|
parser: require.resolve('@typescript-eslint/parser-v3'),
|
|
parserOptions,
|
|
}).run(
|
|
'parser: @typescript-eslint/parser@3.x',
|
|
ReactHooksESLintRule,
|
|
testsTypescriptEslintParser
|
|
);
|
|
|
|
new ESLintTester({
|
|
parser: require.resolve('@typescript-eslint/parser-v4'),
|
|
parserOptions,
|
|
}).run('parser: @typescript-eslint/parser@4.x', ReactHooksESLintRule, {
|
|
valid: [
|
|
...testsTypescriptEslintParserV4.valid,
|
|
...testsTypescriptEslintParser.valid,
|
|
],
|
|
invalid: [
|
|
...testsTypescriptEslintParserV4.invalid,
|
|
...testsTypescriptEslintParser.invalid,
|
|
],
|
|
});
|
|
|
|
new ESLintTester({
|
|
parser: require.resolve('@typescript-eslint/parser-v5'),
|
|
parserOptions,
|
|
}).run('parser: @typescript-eslint/parser@^5.0.0-0', ReactHooksESLintRule, {
|
|
valid: [
|
|
...testsTypescriptEslintParserV4.valid,
|
|
...testsTypescriptEslintParser.valid,
|
|
],
|
|
invalid: [
|
|
...testsTypescriptEslintParserV4.invalid,
|
|
...testsTypescriptEslintParser.invalid,
|
|
],
|
|
});
|
|
});
|