Skip to content

Instantly share code, notes, and snippets.

@nilscox
Created January 25, 2023 13:12
Show Gist options
  • Save nilscox/a0fa520f72435278700b9fcfbd27a603 to your computer and use it in GitHub Desktop.
Save nilscox/a0fa520f72435278700b9fcfbd27a603 to your computer and use it in GitHub Desktop.
React hook to wait for some check to be true
import { act, renderHook } from '@testing-library/react'
import { useWaitFor } from './use-wait-for'
describe('useWaitFor', () => {
beforeEach(() => {
jest.useFakeTimers()
})
it('calls then when the condition is true', () => {
let check = jest.fn().mockReturnValue(false)
const onTimeout = jest.fn()
const then = jest.fn()
const { result, rerender } = renderHook(() => useWaitFor({ check, then, timeout: 1, onTimeout }, [check]))
act(result.current[0])
expect(then).not.toHaveBeenCalled()
check = jest.fn().mockReturnValue(true)
rerender({ check, then })
act(() => void jest.runAllTimers())
expect(then).toHaveBeenCalled()
expect(onTimeout).not.toHaveBeenCalled()
})
it('returns the pending flag to true when the timeout is running', () => {
const check = jest.fn().mockReturnValue(false)
const onTimeout = jest.fn()
const then = jest.fn()
const { result, rerender } = renderHook(() => useWaitFor({ check, then, timeout: 1, onTimeout }, [{}]))
expect(result.current[1]).toBe(false)
act(result.current[0])
expect(result.current[1]).toBe(true)
check.mockReturnValue(true)
rerender({ check })
act(() => void jest.runAllTimers())
expect(result.current[1]).toBe(false)
act(result.current[0])
act(() => void jest.runAllTimers())
expect(result.current[1]).toBe(false)
})
it('does not set the timeout when the condition is already true', () => {
const check = jest.fn().mockReturnValue(true)
const onTimeout = jest.fn()
const then = jest.fn()
const { result } = renderHook(() => useWaitFor({ check, then, timeout: 1, onTimeout }, [{}]))
const [execute] = result.current
expect(result.current[1]).toBe(false)
act(execute)
expect(result.current[1]).toBe(false)
expect(then).toHaveBeenCalled()
})
it('calls onTimeout when the condition remains false after a timeout', () => {
const check = jest.fn().mockReturnValue(false)
const onTimeout = jest.fn()
const then = jest.fn()
const { result } = renderHook(() => useWaitFor({ check, then, timeout: 1, onTimeout }, [check]))
act(() => {
result.current[0]()
jest.runAllTimers()
})
expect(onTimeout).toHaveBeenCalled()
expect(then).not.toHaveBeenCalled()
})
it('does not call onTimeout when the condition is true', () => {
const check = jest.fn().mockReturnValue(true)
const onTimeout = jest.fn()
const then = jest.fn()
const { result, rerender } = renderHook(() => useWaitFor({ check, then, timeout: 1, onTimeout }, [{}]))
act(result.current[0])
rerender()
act(() => void jest.runAllTimers())
expect(then).toHaveBeenCalled()
expect(onTimeout).not.toHaveBeenCalled()
})
})
import { DependencyList, useEffect, useState } from 'react'
type WaitForOptions = {
check: () => boolean
then: () => void
timeout: number
onTimeout: () => void
}
type WaitForResult = [() => void, boolean]
export const useWaitFor = (
{ check, then, timeout, onTimeout }: WaitForOptions,
deps: DependencyList
): WaitForResult => {
const [timeoutId, setTimeoutId] = useState<number>()
useEffect(() => {
if (timeoutId === undefined) {
return
}
if (check()) {
then()
window.clearTimeout(timeoutId)
setTimeoutId(undefined)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps)
const onStart = () => {
if (check()) {
then()
} else {
setTimeoutId(
window.setTimeout(() => {
onTimeout()
setTimeoutId(undefined)
}, timeout)
)
}
}
return [onStart, timeoutId !== undefined]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment