Skip to content

Commit d863244

Browse files
committed
feat: impl hooks
1 parent 8b417fa commit d863244

5 files changed

Lines changed: 133 additions & 0 deletions

File tree

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './usePolling'
2+
export * from './usePollingForce'

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Controls } from './usePollingControl'
2+
3+
export type PollingConfig = {
4+
onError?: () => void
5+
}
6+
7+
export type PollingControls = Omit<Controls, 'isPausedRef'>

src/usePolling.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { useCallback, useEffect, useRef } from 'react'
2+
import type { PollingConfig, PollingControls } from './types'
3+
import { usePollingControl } from './usePollingControl'
4+
5+
export const usePolling = <T extends (...args: never[]) => Promise<void>>(
6+
callback: T,
7+
timeout: number,
8+
config: PollingConfig = {}
9+
): PollingControls => {
10+
const timerIdRef = useRef<ReturnType<typeof window.requestAnimationFrame>>()
11+
const callbackRef = useRef<T>(callback)
12+
const pollTimerRef = useRef<number>()
13+
14+
const { isPausedRef, ...controls } = usePollingControl()
15+
16+
const { onError } = config
17+
const interval = timeout < 0 ? 0 : timeout
18+
19+
useEffect(() => {
20+
callbackRef.current = callback
21+
}, [callback])
22+
23+
const loop = useCallback(
24+
async (time: number): Promise<void> => {
25+
if (!pollTimerRef.current) {
26+
pollTimerRef.current = time
27+
}
28+
const elapsed = time - pollTimerRef.current
29+
if (elapsed >= interval) {
30+
if (!isPausedRef.current) {
31+
await callbackRef.current().catch(() => {
32+
onError?.()
33+
})
34+
pollTimerRef.current = undefined
35+
}
36+
}
37+
timerIdRef.current = window.requestAnimationFrame(loop)
38+
},
39+
[interval, isPausedRef, onError]
40+
)
41+
42+
useEffect(() => {
43+
timerIdRef.current = window.requestAnimationFrame(loop)
44+
45+
return () => {
46+
if (timerIdRef.current) {
47+
window.cancelAnimationFrame(timerIdRef.current)
48+
}
49+
}
50+
}, [loop])
51+
52+
return controls
53+
}

src/usePollingControl.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { type MutableRefObject, useCallback, useRef, useState } from 'react'
2+
3+
export type Controls = {
4+
isPaused: boolean
5+
isPausedRef: MutableRefObject<boolean>
6+
pause: () => void
7+
resume: () => void
8+
}
9+
10+
export const usePollingControl = (): Controls => {
11+
const [isPaused, setIsPaused] = useState<boolean>(false)
12+
const isPausedRef = useRef(false)
13+
14+
const pause = useCallback(() => {
15+
isPausedRef.current = true
16+
setIsPaused(true)
17+
}, [setIsPaused])
18+
19+
const resume = useCallback(() => {
20+
isPausedRef.current = false
21+
setIsPaused(false)
22+
}, [setIsPaused])
23+
24+
return {
25+
isPaused,
26+
isPausedRef,
27+
pause,
28+
resume,
29+
}
30+
}

src/usePollingForce.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useCallback, useEffect, useRef } from 'react'
2+
import type { PollingConfig, PollingControls } from './types'
3+
import { usePollingControl } from './usePollingControl'
4+
5+
export const usePollingForce = <T extends (...args: never[]) => Promise<void>>(
6+
callback: T,
7+
timeout: number,
8+
config: PollingConfig = {}
9+
): PollingControls => {
10+
const timerIdRef = useRef<number>()
11+
const callbackRef = useRef<T>(callback)
12+
13+
const { isPausedRef, ...controls } = usePollingControl()
14+
15+
const { onError } = config
16+
const interval = timeout < 0 ? 0 : timeout
17+
18+
useEffect(() => {
19+
callbackRef.current = callback
20+
}, [callback])
21+
22+
const loop = useCallback(async () => {
23+
if (!isPausedRef.current) {
24+
await callbackRef.current().catch(() => {
25+
onError?.()
26+
})
27+
}
28+
}, [isPausedRef, onError])
29+
30+
useEffect(() => {
31+
timerIdRef.current = window.setInterval(loop, interval)
32+
33+
return () => {
34+
if (timerIdRef.current) {
35+
window.clearInterval(timerIdRef.current)
36+
}
37+
}
38+
}, [interval, loop])
39+
40+
return controls
41+
}

0 commit comments

Comments
 (0)