0

I have a timer component which is the child component of A. I'm having play and pause as state values of the parent and passing it down to the child.

I would like to start a setTimeout for the child component when play is true, the child will get the total duration time from the hook state.

I'm using hooks to store the total Duration in the child and having a setTimeout function inside the useEffect and if the value of play is true and then overwriting the hook state value by decrementing the initial value by 1.

https://stackblitz.com/edit/react-nlxodz has 2 timers, wanted to know if what im doing in ShowTimer is a good approach or not, the cleanup function of the useEffect is called for every State update and the Timer component is not in sync with the ShowTimer even if they both start off with 10 seconds.

visizky
  • 701
  • 1
  • 8
  • 27

1 Answers1

1

I would say no, this is not a good approach to set a Timer, though your showTimer approach is close.

Both setTimeout() or setInterval() are not accurate and cannot be trusted. A better way would be using Date.now() approach:

const App = () => {
  const [isStart, setIsStart] = React.useState(false)
  const [duration, setDuration] = React.useState(10)
        
  const handleStart = () => setIsStart(!isStart)
  
  React.useEffect(() => {
    let interval = null
    if (!isStart || duration === 0) return
    else {
      const now = Date.now() + duration * 1000
      interval = setInterval(() => {
        const secondLeft = Math.round((now - Date.now()) / 1000)
        setDuration(secondLeft)
        if (secondLeft === 0) {
          setIsStart(false)
          clearInterval(interval)
        }
      }, 1000)
    }
    console.log("Effect render, Called because duration is changes")

    return () => {
      clearInterval(interval)
      console.log("Cleanup Effect")
    }
  }, [duration, isStart])
  
  return (
    <div className="App">
      <button onClick={handleStart} style={{ marginRight: 10 }}>
        {isStart && duration > 0 ? "Pause" : "Start"}
      </button>
      <span>{duration}</span>
    </div>
  )
}

ReactDOM.render( <App /> , document.getElementById('root'))
<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 id="root"></div>
awran5
  • 4,333
  • 2
  • 15
  • 32
  • could you shed some light on as to why the cleanup effect is fired after each setTimeout call? – visizky Jan 14 '20 at 06:28
  • That happens basically because of the `useEffect` dependencies. Please note that we set the (whole) `useEffect` hook runs whenever the timer seconds changes or button is been clicked. Please read [Docs](https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1) about that. – awran5 Jan 14 '20 at 06:37
  • Take a look at my [codeSandbox](https://codesandbox.io/s/react-timer-using-hooks-fxxms) To see for yourself, go ahead and remove `duration` from the `useEffect` dependencies. Though it will still work, you'll get eslint "warning" about missing dependencies. Now you can remove the duration `check` and hard-coded it `Date.now() + 10 * 1000` then it wont be needed as a dependency. – awran5 Jan 14 '20 at 06:54