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.
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.