Search code examples
javascriptreactjsd3.jsfrontend

Best way of calling conditioned function just once in useEffect if frequent changes in dependency array?


I use "Scrollytelling" in my React application and need to change an svg dynamically according to the current scrollprogress of a section. For example like this:

  useEffect(() => {
    if(scrollProgress > 0.5 && scrollProgress < 0.7) foo(); // Called at each change of scrollProgress
    if(scrollProgress > 0.7 && scrollProgress < 0.9) bar(); // Called at each change of scrollProgress
    // etc.
  }, [scrollProgress]);

But I do not want to call these functions at every change of scrollProgress which changes very frequently, I only want to call it once (and not at the first render of the element then I would use an empty dependeny-Array). Surely this has to be something that happens all the time. I cannot imagine to make a state variable "hasCalled" for each function I want to call in such a scenario and change it accordingly.

Heres an example of where I want to draw an arrow only if the scrollProgress reaches a certain threshold (problem is, it keeps calling the drawArrow() function as scrollProgress keeps on changing)

Demonstration

What is the proper way to handle something like this?


Solution

  • By initialize a ref with start, end, action ( which is the function will get executed ), called ( flag to determine if it get called or not ), then check the scrollProgress is in the range and didn't get called, then execute the action, and then mark the called to be true.

    function Component({ scrollProgress }) {
    
      const actions = useRef([
        { start: 0.5, end: 0.7, action: action1, called: false },
        { start: 0.7, end: 0.9, action: action2, called: false },
        // Add more actions if needed
      ]).current;
    
      useEffect(() => {
        actions.forEach((item) => {
          if (scrollProgress > item.start && scrollProgress < item.end && !item.called) {
            item.action();
            item.called = true;  // Mark as called
          } else if (scrollProgress <= item.start || scrollProgress >= item.end) {
            item.called = false; // Reset if out of range
          }
        });
      }, [scrollProgress, actions]);
    
      return <div>Current Scroll: {scrollProgress}</div>;
    }