useEffect used for countdown timer, re-renders whole page each second

Viewed 1037

In a React app, I created countdown timer:

  const [showSec, setShowSec] = useState(15);

  useEffect(() => {
    const timer =
      showSec > 0 && setInterval(() => setShowSec(showSec - 1), 1000);
    if (showSec === 0) {
      setShowEnterCode(false);
    }
    return () => clearInterval(timer);
  }, [showSec]);

I show it like that:

<span>{showSec}</span>

and in another place in my code I have something like this for showing some errors:

  {props.args.errorFormat === 2 ? (
     <ErrorSnackBar args={ErrorSnackbarArgs} />
  ) : (
     <></>
  )}

But my problem is that, every time that countdown counts, this part of code executes again. It seems re rendering happens. for example when that condition is true, It should show an error with Material-UI's SnackBar but because of that countdown timer, it restart showing the error constantly. I cant prevent this issue. what I have to do?

1 Answers

You can try the following code, it has Timer as pure component and you can pass in setShowEnterCode and optionally seconds. Make sure that setShowEnterCode in App does not change when App re renders:

const { useEffect, useState } = React;

//make this a pure component so it won't re render when props
//  don't change and parent re renders. It re renders when
//  local state changes
const Timer = React.memo(function Timer({
  setShowEnterCode,
  seconds = 15,
}) {
  const [showSec, setShowSec] = useState(seconds);
  //you can pass in seconds (default is 15) and it'll
  //  set the initial duration
  useEffect(() => setShowSec(seconds), [seconds]);
  useEffect(() => {
    const timer =
      showSec > 0 &&
      //effect runs every time showSec changes so
      //  no need for the interval it only runs once
      setTimeout(() => setShowSec(showSec - 1), 1000);
    if (showSec === 0) {
      setShowEnterCode(false);
    }
    return () => clearInterval(timer);
    //make sure setShowEnterCode does not change
    //  when App re renders
  }, [setShowEnterCode, showSec]);
  return <span>{showSec}</span>;
});

function App() {
  const [count, setCount] = React.useState(1);
  const [showEnterCode, setShowEnterCode] = React.useState(
    true
  );
  //this effect causes App to re render 10 times a second
  React.useEffect(() => {
    const i = setInterval(() => {
      setCount((c) => c + 1);
    }, 100);
    return () => clearInterval(i);
  });
  return (
    <div>
      <div>app counter:{count}</div>
      {showEnterCode && (
        <Timer setShowEnterCode={setShowEnterCode} />
      )}
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>

Related