admin管理员组

文章数量:1201593

i'm trying to do countdown timer with react. It will be basically countdown from 10 to 0 and when 0 i will call some function.

i found ideally for me some example: but it's a class component i wan't to do that with functional component and hooks but i can't.

i tried:

function App() {
  const [seconds, setSeconds] = useState(10);
  useEffect(() => {
    setSeconds(setInterval(seconds, 1000));
  }, []);

  useEffect(() => {
    tick();
  });

  function tick() {
    if (seconds > 0) {
      setSeconds(seconds - 1)
    } else {
      clearInterval(seconds);
    }
  }

  return (

    <div className="App">
      <div
        {seconds}
      </div>
    </div>
  );
}

export default App;

it's count down from 10 to 0 very quickly not in 10 seconds. where i mistake ?

i'm trying to do countdown timer with react. It will be basically countdown from 10 to 0 and when 0 i will call some function.

i found ideally for me some example: https://codesandbox.io/s/0q453m77nw?from-embed but it's a class component i wan't to do that with functional component and hooks but i can't.

i tried:

function App() {
  const [seconds, setSeconds] = useState(10);
  useEffect(() => {
    setSeconds(setInterval(seconds, 1000));
  }, []);

  useEffect(() => {
    tick();
  });

  function tick() {
    if (seconds > 0) {
      setSeconds(seconds - 1)
    } else {
      clearInterval(seconds);
    }
  }

  return (

    <div className="App">
      <div
        {seconds}
      </div>
    </div>
  );
}

export default App;

it's count down from 10 to 0 very quickly not in 10 seconds. where i mistake ?

Share Improve this question edited Dec 12, 2019 at 21:48 Brett DeWoody 62.7k31 gold badges144 silver badges192 bronze badges asked Dec 12, 2019 at 20:57 user3348410user3348410 2,83311 gold badges55 silver badges91 bronze badges 2
  • Why use a hook? This is basically just as much code as a regular component. – Mike 'Pomax' Kamermans Commented Dec 12, 2019 at 21:05
  • 1 @Mike'Pomax'Kamermans While it's about the same amount of code (at least in the current setup), there are other reasons to prefer a functional component over a class component. Functional components are often easier to read, easier to test, and the React team claims there might be performance incentives in future versions of React. – Brett DeWoody Commented Dec 12, 2019 at 21:30
Add a comment  | 

4 Answers 4

Reset to default 17

It appears the multiple useEffect hooks are causing the countdown to run more than once per second.

Here's a simplified solution, where we check the seconds in the useEffect hook and either:

  • Use setTimeout to update seconds after 1 second, or
  • Do something else (the function you want to call at the end of the countdown)

There are some downsides to this method, see below.

function App() {
  const [seconds, setSeconds] = React.useState(10);

  React.useEffect(() => {
    if (seconds > 0) {
      setTimeout(() => setSeconds(seconds - 1), 1000);
    } else {
      setSeconds('BOOOOM!');
    }
  });

  return (
    <div className="App">
      <div>
        {seconds}
      </div>
    </div>
  );
}

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

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

Downsides

Using setInterval has the downside that it could be stopped - for example, the component is unmounted, you navigate to a different tab, or close your computer. If the timer requires more robustness, the better alternative would be to store an endTime in the state (like a global store or context) and have your component check the current time against the endTime to calculate the countdown.

Do you care about precision? If so, you don't want setInterval. If you don't care about precision (and you probably don't) then you can schedule a call to tick() on an interval, not the other way around.

const TimeoutComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { countdown: 10 };
    this.timer = setInterval(() => this.tick(), props.timeout || 10000);
  }

  tick() {
    const current = this.state.countdown;
    if (current === 0) {
      this.transition();
    } else {
      this.setState({ countdown: current - 1 }); 
    } 
  }

  transition() {
    clearInterval(this.timer);
    // do something else here, presumably.
  }

  render() {
    return <div className="timer">{this.state.countDown}</div>;
  }
}

This depends on your logic a little bit. In the current situation your useEffect where you run your tick method is running on every render. You can find a naive example below.

function App() {
  const [seconds, setSeconds] = useState(10);
  const [done, setDone] = useState(false);
  const foo = useRef();

  useEffect(() => {
    function tick() {
        setSeconds(prevSeconds => prevSeconds - 1)
    }
    foo.current = setInterval(() => tick(), 1000)
  }, []);

  useEffect(() => {
    if (seconds  === 0) {
      clearInterval(foo.current);
      setDone(true);
    }
  }, [seconds])

  return (

    <div className="App">
    {seconds}
    {done && <p>Count down is done.</p>}
    </div>
  );
}

In the first effect we are doing the countdown. Using callback one for setting state since interval creates a closure. In the second effect we are checking our condition.

Simply use this snippet, As it will also help to memoize the timeout callback.

const [timer, setTimer] = useState(60);    
const timeOutCallback = useCallback(() => setTimer(currTimer => currTimer - 1), []);

useEffect(() => {
  timer > 0 && setTimeout(timeOutCallback, 1000);
}, [timer, timeOutCallback]);

console.log(timer);

Hope this will help you or somebody else.

Happy Coding!

本文标签: javascriptHow make react countdown timerStack Overflow