react: [react-hooks/exhaustive-deps] Does not complain for nested dependencies if the parent object is added in dependencies array

React version: 17.0.0-rc.0

Steps To Reproduce

  1. add a useEffect which does something with history.location.hash and history.push
  2. The eslint hook complains that you should have a dependency on history. I understand that is because we are passing history as this to the push function
  3. If you add history to dependencies, it will stop complaining, although history.location.hash can change without history object itself being changed.

Link to code example: https://codesandbox.io/s/react-router-usehistory-hooks-forked-2p5mk?file=/src/App.js

Screenshot 2020-08-18 at 12 56 42 PM Screenshot 2020-08-18 at 12 56 50 PM

The current behavior

  • lint rule does not complain or suggest anything related to destructuring the nested properties, or adding them explicitly after the top level object is added

The expected behavior

  • if possible, it can show similar suggestions as it does when the whole props object is specified as a dependency

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 18 (9 by maintainers)

Most upvoted comments

I don’t really understand how location.hash is different from any other prop (say props.hash),

I don’t know whether location you’re referring to is a mutable object or not so it’s hard for me to say.

location is immutable , it comes from react router.

https://reactrouter.com/web/api/history#:~:text=The history object is mutable,are correct in lifecycle hooks.

In this case, the new location.hash would be equivalent to what we wanted to push in history, we can probably have a check that we should not push if the hash is already present.

This sounds reasonable. You can usually do something like this with refs.

In general, the guideline is that effects should be resilient to occasionally re-running. If it is super important that the code inside doesn’t reexecute unless some specific case happens, it is best to code it explicitly.

Just to clarify, by this you mean that the react component would not re-render if the object is mutated, because it can only re-render based on props or state, and so the effect would not re-run anyway ?

Yes.

when history.location.hash changes, the component re-renders because of the Router.

This sounds somewhat fragile. If it re-renders because some router prop changed, then you should use that prop/context to determine “where you are” rather than reading it from a mutable object.

If I remove history.push usage , then the lint rule complains that history.location.hash should be specified in dependencies,

The lint rule can’t guess we’re dealing with a mutable object in this case, so it assumes this is a regular piece of data.

Or is that a general guideline to be followed that if you are using an object in dependencies, for which the nested properties might change without the object itself changing, you might need to add extraneous dependencies yourself to the deps array.

If you’re using a mutable object in dependencies, specifying its properties as dependencies is going to lead to confusing behaviors because you can never be sure whether it was updated “just in time” before a React render or slightly afterwards (in which case you’ll “miss” the chance to respond to it).

In general, reading mutable data during render (which is what you’re doing when you read history.something to put it in dependency list) is a fragile pattern.