React simple polling custom hook usePollingEffect.

Pablo Garcia
2 min readApr 15, 2022

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(...)

--

--

Pablo Garcia

Senior Engineer at Netflix, ex-Staff Architect 2 at PayPal. M.S. in Computer Science w/specialization in Computing Systems. B.Eng. in Computer Software.