0

I'm using the CircularProgress component from material-ui and my own component that animates the circular progress icon using setInterval. After each interval, the progress icon redraws. When the circle closes, it resets -and- it attempts to notify the parent using a callback prop.

This design produces the well understood warning,

Warning: Cannot update a component (App) while rendering a different component (RefreshTimer).

But I have not seen a solution when setInterval() is used. In the below case, the timer has two jobs that impact both the parent and the child. Yes, I can elevate the timer to the parent, but I suspect there is a better solution and good opportunity for me to learn something.

Code fragments...

App component:

export default function App()  {
  const [refreshFlag, setRefreshFlag] = useState(false);

  const handleRefresh = useCallback(() => {
    setRefreshFlag(!refreshFlag);
    console.log('setting refresh flag?!?');
  }, [refreshFlag]);
    
  useEffect(() => {
    console.log('trigger App refresh!');
  }, [refreshFlag]);

  return(<div>
      <RefreshTimer handleRefresh={handleRefresh}/>
  </div>)
}

RefreshTimer component:

export default function RefreshTimer({handleRefresh}) {
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setProgress((prevProgress) => {
        if( prevProgress >= 100 ) {
          handleRefresh(true);
          return 0;
        } else {
          return prevProgress + 10;
        }
      });
    }, (REFRESH_RATE*1000/10));
    return () => {
      clearInterval(timer);
    };
  });

  return <CircularProgressWithLabel value={progress} />;
}

The use of handleRefresh() inside of useEffect() of the child produces the warning since it causes App to re-render.

What is the right design pattern for using setInterval in a child component like this?

Greg
  • 2,559
  • 4
  • 28
  • 46
  • 1
    Using `handleRefresh()` inside of `useEffect()` of the child is perfectly fine. The problem is that you are using `handleRefresh()` inside of the `setProgress` updater function. – Ryan Cogswell Apr 23 '23 at 00:55

1 Answers1

0

As Ryan pointed out, the problem was that I was setting the parent state inside the child state update.

Here is a working solution for the RefreshTimer component:

export default function RefreshTimer({handleRefresh}) {
  const [progress, setProgress] = useState(0);

  const computeProgress = (prevProgress) => {
    return (prevProgress >= 100) ? 0 : prevProgress + 10;
  }

  useEffect(() => {
    const timer = setInterval(() => {
      let newProgress = computeProgress(progress);
      if( newProgress === 0 ) {
        handleRefresh(true);
      }
      setProgress(newProgress);
      }, (REFRESH_RATE*1000/10));
    return () => {
      clearInterval(timer);
    };
  });

  return <CircularProgressWithLabel value={progress} />;
}
Greg
  • 2,559
  • 4
  • 28
  • 46