'Reactjs setInterval with scroll only trigger once

I have auto scroll function and scroll will start when user click the function as follow.

It is scroll 50px to y axis once when user click play button. But it is only scroll once even thought I have added interval. Interval is working because I saw the "scrolling" console.log is increasing. But scroll is not scrolling again.

May I know why scroll is not move again?

import React, { useState, useEffect, useRef } from "react";
import { useParams, NavLink } from "react-router-dom";
import { useQuery } from "@apollo/client";
import { getTrack, getTrackVariable } from "../../gql/track";
import ChordSheetJS from "chordsheetjs";
import {
  YoutubeIcon,
  FacebookIcon,
  PlayIcon,
  PauseIcon,
} from "../../assets/icons/svg_icons";
import { SettingIcon } from "../../assets/icons/svg_icons";
import paths from "../../routes/paths";
import { FacebookShareButton } from "react-share";
import GoTop from "../../components/go_top";

const TrackPage = () => {
  const intervalId = useRef(null);
  const { trackId } = useParams();
  const [track, setTrack] = useState();
  const [collapse, setCollapse] = useState(true);
  const [play, setPlay] = useState(false);
  const [speed, setSpeed] = useState(1);
  const { loading, error, data } = useQuery(getTrack, {
    variables: getTrackVariable(trackId),
  });

  const trackRef = useRef();

  useEffect(() => {
    if (!loading && !error) {
     setTrack(data?.track);
   }
 }, [loading, error, data]);


 const getChordSheet = (value) => {
   const parser = new ChordSheetJS.ChordProParser();
   const song = parser.parse(value);
   const formatter = new ChordSheetJS.HtmlTableFormatter();
   const chordSheet = formatter.format(song);
   return chordSheet;
    };

 const handleError = (e) => {
   e.target.onerror = null;
   e.target.src = Monk;
   };

  const handleMenuCollapse = (e) => {
    e.preventDefault();
    setCollapse(!collapse);
    };

  const handleSpeedUp = () => {
    setSpeed(speed + 1);
   };

  const handleSpeedDown = () => {
    setSpeed(speed - 1);
   };

  const handleScroll = () => {
    setPlay(!play);

     if (play) {
      console.log("stop");
      clearInterval(intervalId.current);
    } else {
      let delayInMs = 100;
      const onScrollStep = () => {
        document.getElementById("content").scroll(0,50);
        console.log("srolling")
      };
      intervalId.current = setInterval(onScrollStep, delayInMs);
      console.log("play");
       }
        };

  return (
    <>
  <div id="setting">
    {/** the big div */}
    <div
      className={` w-36 h-56  bg-primary absolute top-[calc((100vh-384px)/2)]  ${
        collapse ? "hidden" : "right-0"
      } " bg-primary rounded-b-lg items-center justify-center`}
    >
      <div>
        <div className="items-center justify-center mt-5">
          <div className="flex text-xs items-center justify-center ">
            <span className=" text-sm text-white">Scroll</span>
          </div>
          <div className="flex text-xs pt-0 mt-0 items-center justify-center ">
            <button
              className="px-2 btn-sm flex w-20 items-center bg-transparent hover:bg-accent border text-white font-semibold hover:text-white border-white hover:border-transparent rounded "
              onClick={handleScroll}
            >
              {play ? (
                <PauseIcon className="text-white mr-2" />
              ) : (
                <PlayIcon className="text-white mr-2" />
              )}
              {play ? <span>Pause</span> : <span>Play</span>}
            </button>
          </div>
          <div className="flex text-xs items-center justify-center mt-2">
            <button
              className="w-auto bg-transparent mr-2 hover:bg-accent text-white font-semibold hover:text-white py-1 px-2 border border-white hover:border-transparent rounded"
              onClick={handleSpeedDown}
            >
              -1
            </button>
            <button
              className="w-auto bg-transparent ml-2 hover:bg-accent text-white font-semibold hover:text-white py-1 px-2 border border-white hover:border-transparent rounded"
              onClick={handleSpeedUp}
            >
              +1
            </button>
          </div>
        </div>
      </div>
    </div>

    {/** the icon div */}
    <div
      className={`flex w-12 absolute  top-[calc((100vh-384px)/2)] h-12 bg-primary
            ${collapse ? "animate-pulse right-0" : "right-36"}
            cursor-pointer bg-primary rounded-l-lg items-center justify-center`}
      onClick={handleMenuCollapse}
    >
      {/* <div className="w-5 h-5 bg-white rounded-full " /> */}
      <SettingIcon />
    </div>
  </div>

  <div id="track" ref={trackRef}>
    
    <div className="flex flex-col w-full py-1 my-1 items-center bg-gray-50">
      <div className="relative my-6 mx-auto md:min-w-[60%] max-h-full">
        {track ? (
          <div className="w-full">
            <pre
              className="px-5 textarea"
              dangerouslySetInnerHTML={{
                __html: getChordSheet(track.lyric),
              }}
            />
          </div>
        ) : (
          <div></div>
        )}
      </div>
    </div>
  </div>
</>
  );
};

export default TrackPage;

app.jsx

import React, { useState, useEffect } from "react";
import Header from "./components/header";
import SideMenu from "./components/side_menu";
import AppRoutes from "./routes";
import withUser from "./hocs/with_user";
import { isMobile } from "react-device-detect";
import { useLocation } from "react-router-dom";
import { AuthProvider, setAccessToken } from "./auth/auth_provider";
import { Toaster } from "react-hot-toast";
import AppContext from "./components/app_context";
import "./i18n";
import "./App.css";


function App(props) {
  const [collapse, setCollapse] = useState(isMobile);
  const [sideBarFull] = useState(true);
  const location = useLocation();

  const IsNormalPage = () => {
     const blankPages = ["/login"];

     for (let i = 0; i < blankPages.length; i++) {
      if (location.pathname.startsWith(blankPages[i])) return 
    false;
    }
    return true;
  };

  useEffect(() => {
    if (props.user) setAccessToken(props.user.t);
  }, []);

  const PageHeader = () => {
    return (
  <div className="h-[72px] w-full flex items-center align-middle justify-center bg-neutral shadow">
    <div className="w-full text-center">
      <Header />
    </div>
  </div>
);
  };


  return (
<AuthProvider user={props.user}>
  <AppContext.Provider
    value={{
      collapse: collapse,
      setCollapse: setCollapse,
    }}
  >
    <div className="relative w-full min-h-screen h-full">
      <div className="flex flex-row min-h-screen">
        <div className="w-auto z-0 ">
          <div className="flex-1 w-full max-h-screen mx-auto text-lg h-full shadow-lg bg-white overflow-y-auto">
            {IsNormalPage() && <SideMenu showFullMenu={sideBarFull} />}
          </div>
        </div>
        <div className="w-full max-h-screen flex flex-col z-10">
          {IsNormalPage() && <PageHeader />}
          <div id="content" className="flex-1 w-full max-h-screen mx-auto text-lg h-full shadow-lg bg-white overflow-y-auto">
            <Toaster />
            <AppRoutes />
          </div>
        </div>
      </div>
    </div>
  </AppContext.Provider>
</AuthProvider>
  );
}

export default withUser(App);


Solution 1:[1]

I think because you are toggling the play state in your component setPlay(!play); Are you trying to scroll to a specific div or just scroll for 50 px in the direction of y-axis? there are two approaches, you can use window or Refs.

an example using the refs to scroll to a specific node in the dom

const ScrollDemo = () => {
   const myRef = useRef(null)

   const executeScroll = () => { myRef.current.scrollIntoView()}

   return (
      <div> 
         <div ref={myRef}>Element to scroll to</div> 
         <button onClick={executeScroll}> Click to scroll </button> 
      <div/>
   )
}


or if you just want to just scroll 50 pixel in the direction of y-axis

  const scrollToTop = () => {
    window.scrollTo(0,50);
  };


return (
        <button onClick={scrollToTop}>
          Go down 50 px!
        </button>
);

Solution 2:[2]

window.scrollTo is only working with html body. document.getElementById is only working overflow div.

useEffect(() => {
    if (play) {
      const onScrollStep = () => {
      var e = document.getElementById("content");
      if (e.scrollHeight - e.scrollTop === e.clientHeight) {
          clearInterval(intervalId.current);
          setPlay(!play);
          return;
    }
    e.scroll(0, e.scrollTop + speed);
  };
  intervalId.current = setInterval(onScrollStep, delayInMs);
} else {
  clearInterval(intervalId.current);
}
},[play, speed])

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Amr
Solution 2 Alex Aung