5

Say I have this code:

useEffect(() => {
  makeBreakfast(pig)
}, [pig, chicken])

makeBreakfast is called on pig, and it makes sense that it's in the dependencies. However in this dummy example, we also want to make breakfast with the pig whenever chicken changes (I don't know why, it's just an example!). We're essentially listening to changes on the chicken, even though it's not involved in the makeBreakfast function.

useEffect(() => {
  if(breakfastMadeRef.current) {
    farmerFed.current = true;
  }
}, [farmerIndex])

In this example, there's no actual dependency, but we do want to check/change the refs on changes to the farmerIndex (again, just a dummy example).

Is there any consequence to this 'extra' dependency? It seems unclean to me, as I'd expect (from the docs, and other places I've seen/used useEffect) that all dependencies would be used in the body of the effect function.

If I use the rule react-hooks/exhaustive-deps, sort of as expected, there's no complaint. So I'm inclined to think this is ok, even though it could be misleading/confusing if we ended up adding the whole farm to the dependencies list?

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173

2 Answers2

5

There are no consequences, but it usually indicates a code smell.

The array provided to useEffect() is better thought of as "this effect synchronizes with this data" rather "run this effect every time this data changes". If the effect doesn't actually use the data provided to it, then it shouldn't need to re-run when it changes.

Usually, extraneous variables provided to useEffect() will end up either being derived state or the variable change is triggered by a function which itself should be the source of the mutation.

It's hard to say much without a more concrete example; the examples you have chosen would be unlikely to show up in the real world.

Dan
  • 10,282
  • 2
  • 37
  • 64
  • I guess the consequence is that is increases code smell. Also, I have definitely seen code similar to both on code bases I've had to work on (I did a minimal tweak to the farmer example)... Largely to do with components that wrap others and use child state (like page index on a resize 'watcher'), but I don't have an actual concrete example to share. – AncientSwordRage Dec 22 '20 at 12:23
  • Here's an example: I have a fancy SVG chart visualization that changes its appearance based on several different props. I want to auto-fit the `viewBox` to the contents of the rendered SVG, but only when *certain* props change; props that greatly affect the geometry of the chart (if I do it on every prop change, it's too jittery). The auto-fit function doesn't actually take any of the props the chart does, it just needs a ref to the rendered SVG element. So, I use a `useEffect` that calls the auto-fit function, and an array of several "extra" dependencies. – V. Rubinetti Jun 15 '22 at 20:37
  • You should use `useLayoutEffect` for this, not `useEffect`. What I said specifically applies to `useEffect`; `useLayoutEffect` is meant to be used for DOM manipulation like you mentioned, and in those cases, that makes sense – Dan Jun 17 '22 at 15:53
-1

The accepted answer is incorrect. Extra dependencies in the useEffect array will cause your effect to fire when they change regardless of whether they are used or not inside the callback. You can try the following component and you can see that console.log() will print everytime the button is clicked even though the dependency is not used in it:

import { useEffect, useState } from "react";

export default function App() {
  const [toggle, setToggle] = useState(false);

  useEffect(() => {
    console.log("Toggled"); // Will print everytime when toggle changes
  }, [toggle]);

  return <button onClick={() => setToggle(!toggle)}>Thing</button>;
}
Alex Vukov
  • 102
  • 6
  • 1
    Please add further details to expand on your answer, such as working code or documentation citations. – Community Aug 29 '21 at 08:20