Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/core/src/useInterval/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,35 @@ describe('useInterval', () => {
jest.advanceTimersByTime(70)
expect(callback).toHaveBeenCalledTimes(1)
})

it('should clear a manually resumed interval on unmount', () => {
const callback = jest.fn()
const { result, unmount } = renderHook(() =>
useInterval(callback, 50, { controls: true }),
)

result.current.resume()
jest.advanceTimersByTime(50)
expect(callback).toHaveBeenCalledTimes(1)

unmount()
callback.mockClear()

jest.advanceTimersByTime(500)
expect(callback).toHaveBeenCalledTimes(0)
})

it('should not leave an orphan interval when resume is called twice', () => {
const callback = jest.fn()
const { result } = renderHook(() =>
useInterval(callback, 50, { controls: true }),
)

result.current.resume()
result.current.resume()
result.current.pause()

jest.advanceTimersByTime(500)
expect(callback).toHaveBeenCalledTimes(0)
})
})
14 changes: 13 additions & 1 deletion packages/core/src/useInterval/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef } from 'react'
import { useEvent } from '../useEvent'
import { useLatest } from '../useLatest'
import { useUnmount } from '../useUnmount'
import { defaultOptions } from '../utils/defaults'
import type { UseInterval } from './interface'

Expand All @@ -18,10 +19,16 @@ export const useInterval: UseInterval = (
const timer = useRef<ReturnType<typeof setInterval> | null>(null)

const clean = () => {
timer.current && clearInterval(timer.current)
if (timer.current) {
clearInterval(timer.current)
timer.current = null
}
}

const resume = useEvent(() => {
// Drop a running timer first — otherwise its id is overwritten below and
// neither pause() nor unmount can reach it again.
clean()
isActive.current = true
timer.current = setInterval(() => savedCallback.current(), delay || 0)
})
Expand Down Expand Up @@ -49,6 +56,11 @@ export const useInterval: UseInterval = (
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [delay, immediate])

// The effect above bails out before registering a cleanup when `controls` is
// set, so a timer started by resume() would outlive the component. Clear it on
// unmount whichever mode the hook runs in.
useUnmount(clean)

return {
isActive,
pause,
Expand Down
Loading