2
votes

I need some help wrapping my head around eslint react-hooks/exhaustive derps. I ONLY want the effect to run when listenToMe has changed, but react-hooks/exhaustive derps is yelling at me to add history. This causes recursion. useEffect was React gold for me until this rule.

ESLint: React Hook useEffect has missing dependencies: 'history'. Either include them or remove the dependency array.(react-hooks/exhaustive-deps)

Can someone help me understand:

  1. Why is it bad practice to only listen for changes you care about in useEffect?
  2. What is the "right" way to only listen for specific changes on state change?
  useEffect(() => {
    if (listenToMe) {
        const search = new URLSearchParams(location.search);
        search.delete('q')
        history.replace({ ...location, search: search.toString() });
      }
    }
  }, [listenToMe]);

I've read through github and react, but I haven't read anything that clicks.

3
React's explanation reactjs.org/docs/… - Jeremiah Muela

3 Answers

1
votes

From the docs

It is only safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them.

The problem arises when you're using passed props or in your case, the history prop coming from a HOC or hook from react-router.

First of all, its passed as a prop, and props can inherently change.

Second of all, you're calling a history.push function. The linter doesn't know that push will always be the same function of the history class and that this function will not use any state or props itself.

The "right" way according to facebook is to move the function inside the effect, but that doesn't make sense if you're just reusing code from some file or using a function like history.push. So I think in your case the solution would be to wrap it in a useCallback with history in its own dependancy array. This is a last resort according to the devs.

Essentially, useCallback will simply return a memoized value instead of actually accessing the value and whenever the value in it's dependancy changes, there is a new callback with new memoized values.

History.push will of course always have the same identity so this somewhat of an anti-pattern.

Personally I have never had any problems passing in values like this. So I think writing a useCallback when you're just dealing with functions declared elsewhere (or any fully stable variable) is pointless and I find it reasonable to skip linting that line.

I keep the rule enabled as others have pointed out however, as its good to keep you on your toes about writing effects.

0
votes

This eslint rule is only a hint useful in most situations, but in some situations you have to ignore it.

React.useEffect(()=> {

  // I have some code with dependencies here but I only want to run it on mount

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

React disabled the auto fixer https://github.com/facebook/react/issues/15204

I installed the new devDependancies (eslint@latest and eslint-plugin-react-hooks)

npm install eslint@latest eslint-plugin-react-hooks@latest --save-dev 

I tested in IntelliJ IDEA 2020.1 (EAP Build #IU-201.5985.32) and it shows the warning, but ESLint Fix does not auto add dependancies to useEffect

ESLint Warning in IntelliJ

ESLint: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array. If 'dispatch' changes too often, find the parent component that defines it and wrap that definition in useCallback.(react-hooks/exhaustive-deps)

VS Code has the "fix" in pre release 2.1.0-next.1, but I have not tested it.

https://github.com/microsoft/vscode-eslint/pull/814#issuecomment-587020489 https://github.com/microsoft/vscode-eslint/releases/tag/release%2F2.1.0-next.1

This does NOT answer the question, but helpfull to anyone who runs into react-hooks/exhaustive-deps auto "fix" issues.