React simple polling custom hook usePollingEffect.
A remarkably simple polling function can be implemented with React hooks.
There are two main differences between a usePollingEffect
and a useInterval
custom hook. First, the polling effect must execute as soon as it is called (contrary to an interval which would trigger the first call only after the specified time has passed). Second, we can’t trigger a new call if the previous call hasn’t finalized, the polling hook must wait until the previous call was finalized while the interval shouldn’t care.
function usePollingEffect(
asyncCallback,
dependencies = [],
{
interval = 10_000 // 10 seconds,
onCleanUp = () => {}
} = {},
) {
const timeoutIdRef = useRef(null)
useEffect(() => {
let _stopped = false
// Side note: preceding semicolon needed for IIFEs.
;(async function pollingCallback() {
try {
await asyncCallback()
} finally {
// Set timeout after it finished, unless stopped
timeoutIdRef.current = !_stopped && setTimeout(
pollingCallback,
interval
)
}
})()
// Clean up if dependencies change
return () => {
_stopped = true // prevent racing conditions
clearTimeout(timeoutIdRef.current)
onCleanUp()
}
}, [...dependencies, interval])
}
This hook tries to execute the async callback. Then it creates a timeout with the expected interval between calls. This takes care of immediately calling the polling function and waiting until the previous call has finalized before creating a timeout.
Usage
const [data, setData] = useState({})
usePollingEffect(
async () => setData(await fetch(...)),
[],
{ interval: 3000, onCleanUp: () => {...} } // optional
)
onCleanUp
is a function executed during the cleanup phase of useEffect
.
We can also use it in conjunction with useMountedState to prevent the Can’t perform a React state update on an unmounted component
error:
const [data, setData] = useMountedState({})
usePollingEffect(
async () => setData(await fetch(...)),
[],
{ interval: 3000 }
)
Enhancements
If you are looking into stopping a polling mechanism, you might also want to cancel a fetch request by Using AbortController with Fetch API and ReactJS:
import { customFetch, abortRequestSafe } from '...'
const KEY = 'HEALTH_CHECK'
healthCheck.abort = () => abortRequestSafe(KEY)
async function healthCheck() {
return await customFetch('/health', { signalKey: KEY })
}usePollingEffect(
async () => await healthCheck(),
[],
{ onCleanUp: () => healthCheck.abort() }
)
Furthermore, we could add a mechanism to kill the poll if we needed it:
function usePollingEffect(...) {
const [dead, kill] = useState(false)
// ...
useEffect(() => {
if(dead) {
return
}
// ...
}, [..., dead])
return [() => kill(true), () => kill(false)]
}const [killPoll, respawnPoll] = usePollingEffect(...)