Search code examples
reactjsreact-hookseslint-plugin-react-hooks

Is React's useEffect dependency array an anti pattern


I have a question regarding useEffect in React and the dependency array. As far as I understand useEffect is there to handle side effects of state changes.

Let's say I'm creating an application like Zoom. So for the receiver call I have code that handles the request for a call in a useEffect when a local state variable called "callState" is equal to answering:

  const [localStream, setLocalStream] = useState()
  const [remoteStream, setRemoteStream] = useState()
  const [remoteRequest, setRemoteRequest] = useState()
  const [currentUser, setCurrentUser] = useState()
  const [callState, setCallState] = useState()
  useEffect(() => {
        const answerCall = async () => {
            console.log("answering")
            if (!remoteRequest || callState !== CallState.Answering) return
            console.log('remoteStream', remoteStream)
            console.log('localStream', localStream)
            console.log('currentUser', currentUser)

            }
            answerCall()
         }, [localStream, remoteStream, remoteRequest, currentUser, callState])

The issue here is that I only want to call the answerCall useEffect when callState changes but it does need to use many of the state variables. I have the conditional if (!remoteRequest || callState !== CallState.Answering) return so I do prevent the useEffect from running if callState isn't answered, however it seems weird that I continuously call a useEffect really only meant to run when callState changes and I need a conditional to bail early if one of the state variables such as localStream changes (if I'm changing the stream to the back facing camera for example). It seems like this design is prone to errors and bugs even if it is more declarative.

I added the console.log('answering') to show my point. If the user logs in, the callState is set to hanging up, the current user refreshes an attribute, the localStream changes.. in all these cases it will log 'answering' to the console.

I can add '// eslint-disable-next-line react-hooks/exhaustive-deps' and only add the callState but there are many articles that warn against this:

https://dev.to/aman_singh/why-effects-shouldn-t-lie-about-their-dependencies-1645

https://betterprogramming.pub/stop-lying-to-react-about-missing-dependencies-10612e9aeeda

What am I missing here?


Solution

  • You need to add only callState into array dependency and move the rest of the logic to a separate method and call the method inside useEffect only when the value of callState is changed.

      const [localStream, setLocalStream] = useState();
      const [remoteStream, setRemoteStream] = useState();
      const [remoteRequest, setRemoteRequest] = useState();
      const [currentUser, setCurrentUser] = useState();
      const [callState, setCallState] = useState();
    
      const answerCall = useCallback(async() => {
         console.log('remoteStream', remoteStream);
         console.log('localStream', localStream);
         console.log('currentUser', currentUser);
         console.log('remoteStream', remoteRequest);
      }, [localStream, remoteStream, remoteRequest, currentUser]);
    
      useEffect(() => {
         (async () => {
          if (callState) {
            answerCall()
          }
         )()
      }, [callState]);