Search code examples
reactjssetintervaluse-effectcodepen

Why do I get an object error when using useEffect and setInterval in my React app?


I'm building a counter with a start/stop button using React. I keep getting an object error on codepen in my functional component. I've narrowed it down to the setInterval line (line 32 in codepen) within useEffect, but I can't figure out what's wrong with it.

Codepen: https://codepen.io/sirstevekim/pen/zYROyxj

const App = () => {
  const [time, setTime] = React.useState(0);
  const [isRunning, setIsRunning] = React.useState(false);
  
  const counter = () => setTime(time + 1);
  
  React.useEffect(() => {
    const interval = null;
  
    if (isRunning === false) {
      interval = setInterval(counter, 1000);
    } else {
      clearInterval(interval);
    }
    return () => {clearInterval(interval)};
  }, [isRunning]);
  
  const handleStartStop = () => {
    setIsRunning(!isRunning);
  }
  
  const handleReset = () => {
    setIsRunning(false);
    setTime(0);
  }
  
  return (
    <div className="app">
      <Timer time={time} />
      <ControlButtons
        handleStartStop={handleStartStop}
        handleReset={handleReset}
      />
    </div>
  );
}

Solution

  • Few issues to be corrected,

    1. Your counter should be using a callback function otherwise it won't update due to a closure issue (it always sees 0 and adds 1 to it).
    const counter = () => setTime((prevTime) => prevTime + 1);
    
    1. interval should be let instead of const since you reassign it.
    let interval = null;
    

    Working Demo

    const Timer = (props) => {
      return <div className="timer">{props.time}</div>;
    };
    
    const ControlButtons = (props) => {
      return (
        <div>
          <button className="start-stop" onClick={props.handleStartStop}>
            Start/Pause
          </button>
          <button className="reset" onClick={props.handleReset}>
            Reset
          </button>
        </div>
      );
    };
    
    const App = () => {
      const [time, setTime] = React.useState(0);
      const [isRunning, setIsRunning] = React.useState(false);
    
      const counter = () => setTime((prevTime) => prevTime + 1);
    
      React.useEffect(() => {
        let interval = null;
    
        if (isRunning === false) {
          interval = setInterval(counter, 1000);
        } else {
          clearInterval(interval);
        }
        return () => {
          clearInterval(interval);
        };
      }, [isRunning]);
    
      const handleStartStop = () => {
        setIsRunning(!isRunning);
      };
    
      const handleReset = () => {
        setIsRunning(false);
        setTime(0);
      };
    
      return (
        <div className="app">
          <Timer time={time} />
          <ControlButtons
            handleStartStop={handleStartStop}
            handleReset={handleReset}
          />
        </div>
      );
    };
    
    ReactDOM.render(<App />, document.querySelector('.react'));
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <div class='react'></div>