update scroll position every 8 seconds with useState and useEffect?

I’m using next js for my web application. I want to write function that will update the scroll position using useState. For some reason, the useState isn’t updating itself properly as I also need to calculate the maximum value from the function. Here is my code:

const [lazyProgress,setLazyProgress] = useState(0);
const [lazyProgressMax,setLazyProgressMax] = useState(0);


function lazyProgressUpdate(){
        const scrollTotal = document.documentElement.scrollTop;
        const heightWin = document.documentElement.scrollHeight - document.documentElement.clientHeight;
        const scroll = scrollTotal / heightWin * 100;

        setLazyProgress(scroll);
        if(lazyProgress > lazyProgressMax){
        setLazyProgressMax(lazyProgress);
    }
    console.log(lazyProgress,lazyProgressMax)

}

useEffect(()=>{

  const timer = setInterval(()=>{
          lazyProgressUpdate();
      },8000);
      return ()=>{
      clearInterval(timer);
      }
  },[]);

In the above snippet, lazyProgress and lazyProgressMax are always 0 in the function. So neither of them get updated.

How do i overcome this issue

Answer

Issue

The issue you have is actually a stale enclosure of state in the setInterval callback. This callback closes over the initial state so that’s all it’ll ever see.

Solution

Enqueue the scroll updates and use a separate useEffect hook with a dependency on lazyProgress to check/set the lazyProgressMax state.

const [lazyProgress, setLazyProgress] = useState(0);
const [lazyProgressMax, setLazyProgressMax] = useState(0);

function lazyProgressUpdate(){
  const scrollTotal = document.documentElement.scrollTop;
  const heightWin = document.documentElement.scrollHeight - document.documentElement.clientHeight;
  const scroll = scrollTotal / heightWin * 100;

  setLazyProgress(scroll); // <-- (1) update lazyProgress state
}

useEffect(() => {
  const timer = setInterval(() => {
    lazyProgressUpdate();
  }, 8000);
  return () => {
    clearInterval(timer);
  }
},[]);

useEffect(() => {
  setLazyProgressMax(lazyProgressMax => { // <-- (3) functional state update
    if (lazyProgress > lazyProgressMax) {
      return lazyProgress; // <-- (4a) return new lazyProgress value
    }
    return lazyProgressMax; // <-- (4b) or previous state
  });
}, [lazyProgress]); // <-- (2) lazyProgress as dependency