Skip to main content Link Search Menu Expand Document (external link)

Migration to v4

This guide will help you migrate from v3 (or lower) to v4, here is a list of all changes:

Intervals don’t immediately start on mount anymore

Since v4, intervals won’t start immediately on mount anymore.

With the recent introduction of interval controls (start/stop, pause/resume etc.), all looping hooks could be started manually, but they were still starting immediately on mount like before. Since this is often undesired, all of these hooks (except for the clock) won’t start on mount anymore. At least not by default. It can still be enabled via an option, though.

Examples

Intervals can be started via the returned start callback. Just call it upon a button click or inside a useEffect.

const { start } = useInterval(doSomething, 200)

Starting the interval manually is probably the best idea in most cases – however, if you really want an interval to start immediately on mount, you can still do that by using the new interval options (an additional argument for most hooks), where you can set startOnMount to true, like so:

useInterval(doSomething, 200, { startOnMount: true })

This breaking change affects:

  • useInterval()
  • useCounter()
  • useTimer()
  • useCountdown()
// APIs for the affected hooks
const { start } = useInterval(doSomething, 200, { startOnMount: false })
const [value, { start }] = useCounter({ start: 1, interval: 400, stepSize: 2, startOnMount: false })
const [value, { start }] = useTimer(0, { startOnMount: false })
const [value, { start }] = useCountdown(10, 0, { startOnMount: false })

useCountdown() now has a mandatory end parameter

The countdown hook is now a bit more than just a backwards-timer. It has now a mandatory end-parameter and will automatically stop when it’s reached. Additionally, an event callback will be fired when this happens (see examples below).

Examples

// BEFORE:
// In v3 and below this would have counted down to infinity (or until it's manually stopped)
const [counter] = useCountdown(10)

// AFTER:
// Since v4 this will count down to 0 and then stop by itself
const [counter] = useCountdown(10, 0)

To be notified when the countdown ends, you have to provide a callback to options.onEnd().

// This will print a message when the countdown hits 0
const [counter, { start }] = useCountdown(10, 0, { 
    onEnd: () => console.log('Countdown has reached zero!')
})

The return value of useTimer() has changed

To be aligned with all the other interval hooks like useInterval(), useCounter(), and useCountdown(), useTimer() now supports interval controls (like start, stop, pause etc.), too.

This means instead of…

const timerValue = useTimer(0)

…you now have to write:

const [timerValue] = useTimer(0)

The benefit is that you can now pause and resume a timer like so:

const [timerValue, { pause, resume, isPaused }] = useTimer(0)

return <button onClick={isPaused ? resume : pause}>
  Toggle timer
</button>

Timeouts spawned by useTimeout will not be debounced anymore

Previously, timeouts spawned by useTimeout() were automatically debounced. This is not the case anymore. If you want to debounce your callbacks, there is now a dedicated hook for it, called useDebounce()

Examples

Take a look at the following example:

import { useState } from 'react'
import { useTimeout } from 'react-timing-hooks'

const HelloWorld = () => {
  const [output, setOutput] = useState(null)
  const onButtonClick = useTimeout(() => console.log('clicked'), 1000)

  return <div>
    <button onClick={onButtonClick}>Click me!</button>
    <p>{output}</p>
  </div>
}

If you were to click this button more than once in less than a second, clicked would’ve appeared only once in your console. At least in v3 that was the case. Now you would see it as often as you clicked – every callback is executed.

However, it is often desired – especially for user input – to have it “debounced”. For this there is now a dedicated hook which is called useDebounce().

This new hook does not function exactly the same as the old useTimeout() hook though, because it fires immediately and then blocks consecutive executions until the timeout expires. If you want the callback to fire after the timeout, you have to set the flag options.fireAfterTimeout = true.

import { useState } from 'react'
import { useDebounce } from 'react-timing-hooks'

const HelloWorld = () => {
  const [output, setOutput] = useState(null)
  const onButtonClick = useDebounce(() => console.log('clicked'), 1000)

  return <div>
    <button onClick={onButtonClick}>Click me!</button>
    <p>{output}</p>
  </div>
}