Search code examples
reactjsreact-hookssetintervaluse-effect

setInterval in UseEffect getting call 2/4 times


Hi want to hit useEffect just once, but its happening that its getting called 2-3 times , I tried wrapping the function in another but code starts to brake, can you guys help

need to call successHandler, but it is getting called over n over while it

  const { successHandler } = props; 
  const [completed, setCompleted] = React.useState(10); 
  const fileType = getFileType(props.fileName); 

  React.useEffect(() => { 
         function progress() { 
            setCompleted(prevCompleted => { 
               if (prevCompleted >= 100) { 
                   successHandler(); 
                   return 100; 
               } 
              return prevCompleted + 10; }); 
         } 
         const timer = setInterval(progress, 500); 
 
         return () => { clearInterval(timer); }; 
      }, [successHandler]); 

Solution

  • If useEffect is called multiple times, then it means that something in its dependencies changes. In your case successHandler changes. See where it comes from, maybe you need to use useCallback for it to make sure it doesn't change on re-render.

    For example this will re-run useEffect on each re-render, because f will be a new function each time:

    const f = () => {};
    useEffect(() => { f(); }, [f]);
    

    While this will run useEffect only once, since useCallback memoizes the function and f will not change on each re-render:

    const f = useCallback(() => {}, []);
    useEffect(() => { f(); }, [f]);
    

    Later edit - I missunderstood the question. Try to rewrite it like this:

    const { successHandler } = props;
    const [completed, setCompleted] = React.useState(10);
    const fileType = getFileType(props.fileName);
    
    React.useEffect(() => {
      function progress() {
        setCompleted(prevCompleted => Math.max(100, prevCompleted + 10));
      }
      const timer = setInterval(progress, 500);
      return () => {
        clearInterval(timer);
      };
    }, []);
    
    React.useEffect(() => {
      if (completed === 100) {
        successHandler();
      }
    }, [completed, successHandler]);
    

    This way successHandler should only be called once when completed becomes 100 (given that successHandler does not change).

    You probably want to stop the time when progress is 100 too, so you can do it like this:

    const { successHandler } = props;
    const [completed, setCompleted] = React.useState(10);
    const fileType = getFileType(props.fileName);
    const timer = React.useRef(null);
    
    const stopTimer = React.useCallback(() => {
      if (timer.current) {
        clearInterval(timer.current);
        timer.current = null;
      }
    }, []);
    
    React.useEffect(() => {
      function progress() {
        setCompleted(prevCompleted => Math.max(100, prevCompleted + 10));
      }
      timer.current = setInterval(progress, 500);
      return stopTimer;
    }, [stopTimer]);
    
    React.useEffect(() => {
      if (completed === 100) {
        stopTimer();
        successHandler();
      }
    }, [completed, stopTimer, successHandler]);