How to add missing dependencies to a useEffect hook that is run only once?

690 views Asked by At

React Hook useEffect has missing dependencies: 'myDate' and 'setMyDate'. Either include them or remove the dependency array.

How can missing dependencies be added to a useEffect that is run only once? The following example generates the warning above:

const [ myDate, setMyDate ] = useState(0)

const spinner = useRef(null)
useEffect( () => {
    spinner.current = setInterval( () => {
        d = Date.now()
        if (d < myDate)
            setUpdate(d)
    }, 997 )
}, [])

If I include them, I create an infinite loop, as the setTimeout changes the values of the dependencies:

const spinner = useRef(null)
useEffect( () => {
    spinner.current = setInterval( () => {
        d = Date.now()
        if (d < myDate)
            setMyDate(d)
    }, 997 )
}, [myDate, setMyDate])

If I remove the dependency array, the useEffect runs on each render, and sets up an infinite number of setIntervals:

const spinner = useRef(null)
useEffect( () => {
    spinner.current = setInterval( () => {
        d = Date.now()
        if (d < myDate)
            setMyDate(d)
    }, 997 )
})

I also tried removing the useEffect entirely, thinking that since spinner is a useRef, it would not reassign on each component render... but no:

const spinner = useRef(null)
spinner.current = setInterval( () => {
    d = Date.now()
    if (d < myDate)
        setMyDate(d)
}

Also tried using a functional update approach, like so, but lint error persist and the code doesn't work:

const spinner = useRef(null)
useEffect( () => {
    spinner.current = setInterval( () => {
        d = Date.now()
        setMyDate(d => {
            if (d < myDate)
                setMyDate(d)
        }
    }, 997 )
}, [setMyDate])

I'm stuck... caught between a rock and a hard place! How can I resolve this lint warning?

2

There are 2 answers

0
Michael Abeln On

what seems to work for me is to separate the logic of the interval into a useCallback() hook with no dependencies. This memoizes the function and makes sure that the function is only built on initial component render.

Then, inside of the effect you call the function you built with useCallback(), and provide that to the effect dependencies array. Then the effect will rebuild only if the useCallback() function changes – which it won't because it has no dependencies.

Should remove the linter warnings from react-hooks

import React, {useState, useEffect, useRef, useCallback} from 'react'

export default function IntervalHook() {

  const [ myDate, setMyDate ] = useState(0)
  const spinner = useRef(0)

  const spinnerFn = useCallback(() => {
    spinner.current = setInterval(() => {
      const d = Date.now()
      setMyDate(d)
    }, 997 )
  }, [])

  useEffect(() => {
    spinnerFn()     
    return () => {  // this return statement clears the interval when the component unmounts
      if (spinner.current) {
        clearInterval(spinner.current)
        spinner.current = 0
      }
    }
  }, [spinnerFn])

  return (
    <span>{myDate}</span>
  )
}
3
Valentin Briand On

The solution is to get myDate (named date in my code just to be clear) from the setMyDate function itself instead of passing it as a dependency.

setMyDate is memoized and therefore doesn't need to be passed as a dependency.

const [myDate, setMyDate] = useState(0);
const spinner = useRef(null);

useEffect(() => {
  spinner.current = setInterval(() => {
    const d = Date.now();

    setMyDate(date => {
      if (d < date) {
        return d;
      }
      return date;
    });
  }, 997);
}, []);