javascriptreactjsreact-hooks

Not using useEffect dependencies causing state to not change


I have a question about useEffect and useCallback.

I have react code that will request and load data when you scroll to the bottom of the screen.

export default function ImagePage() {
  const [artData, setArtData] = useState([])
  const [isLoading, setIsLoading] = useState(false)
  const [index, setIndex] = useState(2)

  const url = "https://api.artic.edu/api/v1/artworks?page=1&limit=5"

  const fetchData = useCallback(async () => { // <---CALLBACK HERE
    if (isLoading) return

    setIsLoading(true)

    axios.get(`https://api.artic.edu/api/v1/artworks?page=${index}&limit=5`)
      .then((res) => {
        setArtData(((prevItems) => [... new Set ([...prevItems, ...res.data.data])]))
        setIndex((prevIndex) => prevIndex + 1) <---CHANGING INDEX 
        setIsLoading(false)
      })
      .catch((err) => {console.log(err)})
  }, [index, isLoading])

  useEffect(() => {
    setIsLoading(true)
    axios.get(url)
      .then((res) => {
        setArtData(res.data.data)
        setIsLoading(false)
        console.log("use effect loaded")
      })
      .catch((err) => {
        console.log(err)
      })
  }, [])

  useEffect(() => { // <---USE EFFECT IN QUESTION
    const handleScroll = () => {
      const {scrollTop, clientHeight, scrollHeight} = document.querySelector('#image-page-container')
      if (scrollTop + clientHeight >= scrollHeight - clientHeight) {
        console.log("Fetching data")
        console.log(index)
        fetchData()
      }
    }

    document.querySelector('#image-page-container').addEventListener("scroll", handleScroll)

    return () => {
      document.querySelector('#image-page-container').removeEventListener('scroll', handleScroll)
      }
    }, [fetchData]) <--- If i remove "fetchData" the state "index" won't get updated 

My question is:

As you see at the bottom of the code, the final useEffect() is using the fetchData callback as a dependency. If I remove the dependency, the code works fine however the index state does not get updated.

Can anyone shed light of this curious occurrence?

What I know I know the useEffect dependencies are used to decided when the effect is run (if state of dependency changes). However I'm not yet sure how a callBack can change states. This hole in my knowledge may be where the answer to the strange reaction is.


Solution

  • When you remove fetchData as an external dependency from the last useEffect hook, you are closing over the "instance" of fetchData from the initial render cycle where index had a value of 2.

    Without the fetchData dependency, then handleScroll gets a "copy" of that initial fetchData function then handleScroll is passed as a scroll event listener. The index value closed over in fetchData is used in the URL of the Axios GET request and it never sees any updated value.

    By including the re-memoized fetchData (from the useCallback hook) in the dependency array, the useEffect hook properly cleans up the previous handleScroll "instance" that was used as a scroll event handler and the new "instance" of handleScroll gets the new "instance" of fetchData which has the updated index state value closed over in callback scope.