useEffect missing dependency warning with ref in custom hook

Viewed 686

In React Typescript with the exhaustive-deps rule enabled, when I define a ref and use that inside of an effect, the linter is fine with it:

const stringRef: RefObject<string> = useRef("Hello World!");
  
useEffect(() => {
  console.log(stringRef.current);
}, []) // no warning, the linter detects that I'm using a ref

However, when I put the effect inside of a custom hook, the linter complains that I should include the ref inside of the dependency array:

const stringRef: RefObject<string> = useRef("Hello World!");
  
useCustomHook(stringRef);

// in another-file.ts
const useCustomHook = (ref: RefObject<string>) => {
  useEffect(() => {
    console.log(ref.current);
  }, []) // ESLint: React Hook useEffect has a missing dependency: 'ref'. Either include it or remove the dependency array.(react-hooks/exhaustive-deps)
}

Semantically, nothing changed, however, the linter doesn't recognize that ref is a RefObject (even though I typed it as such).

The big question now is: How can I make the linter know that the given dependency doesn't need to be included in the dependency array without suppressing the warning?

For me, it's a major drawback that this is not possible, because I can't convert my effects into custom hooks without the linter complaining.

Thanks for your help.

3 Answers

You can't configure it out of the box.

The linter (eslint) is a static code analyzer. It only analyzes text patterns without compiling the code, i.e it doesn't know the "meaning" of what is written.

For example, it sees the "use***()" pattern and believes its a custom hook, then it verifies it with Rules of Hooks patterns like not having such text in if statement.

See for yourself:

Reminder: Custom hook is a function with 'use' prefix and a function which uses hooks.

// NOT A CUSTOM HOOK, just a function with 'use' prefix
const useConsole = () => console.log("hello");

// Normal function
const logHello = () => console.log("hello2");

const Component = () => {
  if (true) {
    // Warning - React hook is called conditionally
    useConsole();

    // OK
    logHello();
  }
  return <>Example</>;
};

Edit Eslint Example

But, you always can propose a custom rule to identify RefObject<string> and useEffect in same scope.

From the Typescript compiler side, it doesn't violate anything as the use case matches the useEffect type.

The difference between the useEffect1 and useEffect2 is that stringRef is a constant so by definition const doesn't change but in example 2 ref is a variable that can be changed, so you have to add the argument as a dependency.

You can disable the rule using // eslint-disable-next-line react-hooks/exhaustive-deps otherwise you can move the ref inside of useEffect

useEffect(() => {
   // other code
   ...

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Related