React cookbook · Beginner
Poll a long-running job's status
Show progress for a report generation job until it finishes, then stop polling.
Approach
Use useQuery with a dynamic refetchInterval that returns a number while the job is running and false when it's done or failed. TanStack Query pauses polling in background tabs by default.
Solution
import { useQuery } from "@tanstack/react-query";
type Job = { id: string; status: "queued" | "running" | "done" | "failed"; progress: number; download_url?: string };
export function JobProgress({ jobId }: { jobId: string }) {
const { data: job } = useQuery({
queryKey: ["jobs", jobId],
queryFn: () => fetch(`/api/jobs/${jobId}`).then((r) => r.json() as Promise<Job>),
refetchInterval: (query) => {
const status = query.state.data?.status;
return status === "done" || status === "failed" ? false : 2000;
},
});
if (!job) return <p>Starting…</p>;
if (job.status === "failed") return <p role="alert">The report failed. Please try again.</p>;
if (job.status === "done") return <a href={job.download_url}>Download report</a>;
return (
<div>
<progress value={job.progress} max={100} aria-label="Report progress" />
<span aria-live="polite">{job.progress}%</span>
</div>
);
}
Pitfalls
- Polling forever when the job fails.
- Very short intervals hammering the API.
- Polling for many jobs individually — batch status requests.
Variations
- Server-Sent Events or WebSockets for instant updates.
- Exponential backoff on the interval for long jobs.
Further reading: TanStack Query: useQuery
Reference: https://tanstack.com/query/latest/docs/framework/react/reference/useQuery
More React recipes
- Add optimistic updates to a todo list
- Build an accessible modal dialog
- Build infinite scroll with TanStack Query
- Call a cookie-authenticated API with CSRF protection
- Debounce a search input in React
- Handle errors gracefully with error boundaries
- Implement dark mode that follows the system
- Protect routes in a React SPA