1

It is being mentioned in the react documentation that every thing that is supposed to be changed needs to be present in the dependency array of the useEffect hook.

I could make use of // eslint-disable-next-line react-hooks/exhaustive-deps but this is not the ideal way to do it I think.

But what would you do if you want to trigger a Side Effect only when a certain state changes? Not the other things being used to it?

I have a workaround but that doesn't works if you have multiple side effects listening to unique states.

const [state1, setState1] = useState(1);
const [state2, setState2] = useState(2);

const fetchDataWithState = useCallback(() => {
  action.fetchData({
    state1,
    state2,
  })
}, [state1, state2])

// Effect to listen to the changes of state1
useEffect(() => {
  // Some random work related to state1
  fetchDataWithState()
}), [fetchDataWithState, state1])

// Effect to listen to the changes of state2
useEffect(() => {
  // Some random work related to state2
  fetchDataWithState()
}), [fetchDataWithState, state2])

The above code doesn't work if there are multiple side effects, each specific for a particular state.

If state1 gets changed, the fetchDataWithState will have a different reference, so it will lead to execute the callback in the second useEffect which was supposed to triggered only when the state2 changes.

Or should I use // eslint-disable-next-line react-hooks/exhaustive-deps by not passing fetchDataWithState it in the dependency array.

Raghav Sharma
  • 141
  • 2
  • 13
  • Your current code is equivalent to `useEffect(() => { fetchDataWithState(); fetchDataWithState(); }, [fetchDataWithState]);` Yes, you should listen to the warning. – Patrick Roberts Dec 28 '21 at 07:30
  • @PatrickRoberts rephrased the question. How do I execute the `useEffect` callback only when a single state in it changes? `useEffect(() => { action.fetchData({ state1, state2 }) }, [state1]) ` – Raghav Sharma Dec 28 '21 at 07:33

1 Answers1

2

Store the previous value of state1 in a ref, and only invoke the function if state1 actually changes:

const [state1, setState1] = useState(1);
const [state2, setState2] = useState(2);
const state1PreviousRef = useRef(0); // Any initial value that's the same type as state1, but not the same value

// only invoke action.fetchData if state1 changes:
useEffect(() => {
  // state1 changed from last time
  if (state1 !== state1PreviousRef.current) {
    action.fetchData({state1, state2});
  }

  // update previous to current
  state1PreviousRef.current = state1;
}, [
  state1,
  state2,
  action, // might or might not be necessary, depending on where this is defined (you don't show this in your example)
  state1PreviousRef, // not necessary, but **actually** exhaustive
]);
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • @PatrickRoberts Good call. I didn't write this in an editor: thanks for catching that. – jsejcksn Dec 28 '21 at 08:00
  • This would work but I don't think it's the ideal way since there could be "N" number of states and I would have to have a separate ref for each one of them? And the check too. – Raghav Sharma Dec 28 '21 at 08:19
  • (1/2) If they need to acted on together, then why are they separate states? – jsejcksn Dec 28 '21 at 08:30
  • (2/2) Nothing is preventing you from storing _n_ items in an array or object in a single ref and working with that single collection. – jsejcksn Dec 28 '21 at 08:31
  • There are multiple states as each state has its own use case but when an API request is being made then both of them needs to be added in the requestObject. Anyway, I think you are right on this one I could have the previous state stored in a ref and then act accordingly. However, do you have any other suggestion to handle such a case like I mentioned in this comment? – Raghav Sharma Dec 28 '21 at 09:29
  • 1
    No, not unless you missed [this](https://stackoverflow.com/questions/70503939/how-to-execute-useeffect-hook-callback-only-when-a-single-value-from-the-hook-ca/70504112?noredirect=1#comment124630547_70504112). – jsejcksn Dec 28 '21 at 09:46