Tushar Walzade
Tushar Walzade

Reputation: 3819

React scroll event keeps on firing

I'm working on a react functional components project, wherein I've to increment scroll position programmatically when a user actually scrolls in a div.

for example, I've a div with id testDiv & a user scrolled down a bit, now the scroll position is 100, so I want to programmatically increment it by 1 and make it 101.

Problem statement: The scroll position keeps on incrementing via onScroll handler, so the scrollbar only stops at the end of the element even if we scroll only once.

Expected behaviour: Scroll position should be incremented only once by the onScroll handler if we scroll once on the UI.

What I tried: (dummy code for the reproduction purpose)

import React, { useCallback } from "react";
import ReactDOM from "react-dom";
    
const App = (props) => {
  const onScroll = useCallback((event) => {
    const section = document.querySelector('#testDiv');
    // **The problem is here, scrollTop keeps on incrementing even if we scrolled only once**
    if (section) section.scrollTop = event.scrollTop + 1;
  }, []);
  
  return (
    <div id="testDiv" onScroll={onScroll} style={{ height: "500px", overflowY: "scroll" }}>
      <div>test</div>
      <div className="forceOverflow" style={{height: 500 * 25}}></div>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

Upvotes: 1

Views: 2794

Answers (2)

Nina Tarasiuk
Nina Tarasiuk

Reputation: 11

I realize it might be too late. I faced a similar bug. Adding this style to scroll container worked for me

overflow-anchor: none;

Please see https://stackoverflow.com/a/73514605 for more details.

Upvotes: 1

mamady
mamady

Reputation: 178

What you're looking for is throttling or debouncing the function. It keeps on incrementing because with every bit of scroll, onScroll is being called.

There are many ways to throttle functions in react but I like to use debounce from lodash. If I remember correctly it was about 1.5kb gzipped.

I made you a sandbox. And here is the code:

import React, { useCallback } from "react";
import _debounce from "lodash/debounce";

export const App = (props) => {
  let debouncedOnScroll = useCallback(
    _debounce(() => {
      // YOUR CODE HERE
      console.log("CHECK THE CONSOLE TO SEE HOW MANY TIMES IT'S GETTING CALLED");
    }, 150), []); // Wait 150ms to see if I'm being called again, if I got called before 150ms, don't run me!

  return (
    <div
      id="testDiv"
      onScroll={debouncedOnScroll}
      style={{ height: "500px", overflowY: "scroll" }}
    >
      <div>test</div>
      <div className="forceOverflow" style={{ height: 500 * 25 }}></div>
    </div>
  );
};

By the way, use useRef API instead of document.querySelector. This query selector is getting called with every scroll and it's not the lightest weight on the client computer.

Upvotes: 0

Related Questions