facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.46k stars 26.77k forks source link

How to disable the rule react-hooks/exhaustive-deps? #6880

Closed raRaRa closed 5 years ago

raRaRa commented 5 years ago

I'm trying to disable the rule react-hooks/exhaustive-deps. I've tried adding "react-hooks/exhaustive-deps": "off", to .eslintrc without any luck.

How do I disable this rule? Thanks!

gryn commented 5 years ago
  useEffect(
    () => {
      if (previousLocation === 'foo-child') {
        window.scrollTo(0, scrollPosition);
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [previousLocation],
  );

(the goal here is to only scroll back to the saved location of this page if we are coming from a child page).

Both of these variables are in redux, but the effect should only be run when one of them has changed, not both of them. (effectively scrollPosition is constantly recorded, but meaningless, unless previousLocation has just changed).

Is this an appropriate use of the eslint-disable? Is this the correct usage of an effect?

mcmillion commented 5 years ago

I've read through this thread twice now and I still don't quite grasp a clean and terse way to use useEffect to simulate what I use componentDidMount for (kicking off Redux actions to fetch data, etc). It feels weird that the cleanest solution to this seems to be to go back to just using a class component.

bugzpodder commented 5 years ago

For this issue, we aren't planning to [optionally] disable react-hooks/exhaustive-deps because its advantages outweighs any inconveniences. If you are migrating old code, you are better off fixing these warnings rather than trying to disable them. If you need help, please create a codesandbox rather than a small code snippet and we may be able to help. Thanks.

gryn commented 5 years ago

@bugzpodder Could you please comment on the use case I posted above, thank you.

bugzpodder commented 5 years ago

@gryn please provide a minimal repro using codesandbox.io rather than a code snippet.

samuelematias commented 5 years ago

useEffect(() => { getResults(); // React Hook useEffect has a missing dependency: 'getResults'. Either include it or remove the dependency array react-hooks/exhaustive-deps }, [query]);

https://codesandbox.io/s/vdevr

bovas85 commented 5 years ago

@samuelmataraso in that case you need to add that function as dependancy or declare it either outside of the component or inside the use effect and then call it

bugzpodder commented 5 years ago

@samuelmataraso as @bovas85 said, you can do this:

  useEffect(() => {
    const getResults = async () => {
      const response = await axios.get(
        `http://hn.algolia.com/api/v1/search?query=${query}`
      );
      setResults(response.data.hits);
    };
    getResults();
  }, [query]);
gryn commented 5 years ago

@bugzpodder I'm not sure how complete you would want this example. I have left a large comment explaining the purpose of the effect: https://codesandbox.io/s/infallible-hellman-mdech

bugzpodder commented 5 years ago

Hi @gryn, the codesandbox should be a working app demonstrating real app behavior. Without it, we can't really play around with it and can only guess in terms of what the actual fix to be. If the codesandbox is just a stub, then it defeats the purpose of making it.

gryn commented 5 years ago

@bugzpodder I have updated the example to be "fully functional" (I still have not opted to include redux, but have stored the state in the top level component as a substitute). I have added documentation to the entire file, including what you should do to exercise the critical path.

bluepeter commented 5 years ago

MobX is a pretty good example of where injecting dependencies isn't necessary, but we get the annoying linting warnings with no way to turn them off.

Important realization here is that you will never need to specify dependencies for useEffect because there are simply none. Or to be more precise it does depend on store variable, but that's the same case as for React.useRef which value you shouldn't specify in dependencies also.

bluepeter commented 5 years ago

@bugzpodder ^

bugzpodder commented 5 years ago

@gryn thanks for producing a working example. here is my fork of your sandbox: https://codesandbox.io/s/elegant-austin-qmqob Here are the changes I made: 1) checking location !== previousLocation in ScrollToTop doesn't make sense to me. It should always be different? (maybe this is not the case in your app) 2) scrollPosition should not be stored in redux and passed via props. The reason is that if it changes, the component re-renders because this prop changed. 3) What I did was changing scrollPosition to scrollRef from useRef. You can check out Dan's post on why this makes sense: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

@bluepeter I don't have experience into mobx or why their documentation recommends an empty dependency array. In my experience with redux, the dispatch function never changes but I put it in the dependency array anyway. Does it work if you put the store in the dependency array (fully realizing that the docs did not recommend this approach)

bluepeter commented 5 years ago

@bugzpodder passing the stores into the dependency does seem to work. It seems somewhat inelegant, though.

@FredyC can probably articulate better than I can why it's not recommended to pass in MobX store dependencies into useEffect hooks?

danielkcz commented 5 years ago

MobX "store" has stable referential integrity, it will never change unless doing that on purpose. It does work to specify it in dependencies, but as @bluepeter says, it's highly inelegant as it just clutters the code without any benefit.

I still haven't got into using ESLint with CRA, TSLint is good enough for now and because of these false positives I am probably going to avoid it for a long time. Similarly, @jaredpalmer had issues where it was forcing him to useCallback for unnecessary reasons. So thanks, but no thanks :)

gryn commented 5 years ago

@bugzpodder Jack, thank you for your feedback. I agree that the code is not excellent the way it is! I am working within a large application and sometimes we don't have the luxury of fixing what we find. However, I can fix the issue with the way the scroll saving works (we will still need to store it in redux, since the component recording the position will unmount when the page changes, but I can pass a ref down to the wrapped component, instead of a prop).

My take away from this is that you can use references for "consumable" information, and props for "actable" information. That is, "actable" means it either directly effects rendering, or can directly cause action. "Consumable", on the otherhand, is useful only during an action, and is not the cause of one.

Perhaps there are better words for it, and perhaps I am still missing some subtlety. In anycase, you've helped me move forward and hopefully improved my understanding. Thank you.

gaearon commented 5 years ago

Since my comment about action creators got "buried" in the collapsed thread, I'll post it again here: https://github.com/facebook/create-react-app/issues/6880#issuecomment-488158024. TLDR: Hooks don't mesh very well with the "action creator" pattern. The recommendation is to useDispatch() directly. If you insist on splitting "presentational" and "container" components, you can always split them in two layers yourself, and put the useDispatch calls in the outer ones.