Polling
Problem
You need to refresh data on a fixed interval — showing the latest server state without WebSockets or server-sent events. The interval must pause when the component is destroyed.
Solution
Use setInterval to call qc.fetch() repeatedly and clear the interval in your cleanup function to stop polling when the component is destroyed.
ts
const qc = createQuery({ staleTime: 0 }); // always stale so each call hits the server
function startPolling<T>(key: QueryKey, fn: QueryOptions<T>['fn'], intervalMs: number) {
const tick = async () => {
qc.invalidate(key);
await qc.fetch({ key, fn }).catch(() => {});
};
tick();
const id = setInterval(tick, intervalMs);
return () => clearInterval(id);
}
const stopPolling = startPolling(
['job', jobId],
({ signal }) => api.get<Job>('/jobs/{id}', { params: { id: jobId }, signal }),
3_000,
);
// Stop when job completes
qc.subscribe<Job>(['job', jobId], (state) => {
if (state.data?.status === 'done') stopPolling();
});Pitfalls
- Polling continues even when the browser tab is hidden, wasting bandwidth. Pause on
document.visibilitychangeand resume when the tab becomes visible again. - The interval is measured from the start of each request, not from completion. If a request takes longer than the interval, the next fetch starts immediately with no idle gap.
- Failing to stop polling on component teardown causes fetch callbacks to fire on unmounted state. Always call the disposer returned by
startPolling()in your cleanup function.