# Background fetching indicators

> Show that a query is refreshing with its state's isFetching, count requests in flight with useIsFetching, and leave navigations to the router's pending UI.

A query's first load suspends, so Suspense fallbacks cover it. Later requests (refetches on focus, invalidation, polling) happen in the background, while the old data stays on screen. Read `isFetching` on that query's state. For navigations, prefer the router's pending UI: you usually don't need a global fetch counter.

## One query: isFetching

`isFetching` on the reader's state is `true` while a request for the query is in flight, first loads included:

```tsx
function Post(props: { handle: QueryHandle<typeof postQuery> }) {
	const [postThenable, controls] = useQuery(postQuery, props.handle);
	const post = use(postThenable);
	return (
		<article aria-busy={controls.isFetching}>
			<h1>
				{post.title}
				{controls.isFetching ? <Spinner label="Refreshing" /> : null}
			</h1>
			<p>{post.body}</p>
		</article>
	);
}
```

A component that reads `isFetching` re-renders when a request starts and when it ends; one that doesn't read it doesn't ([tracked state](https://raf-query.katt.dev/docs/render-optimizations.md#tracked-state)). Keep the indicator in a small component of its own, and pass the data down to `memo` children (or let [React Compiler](https://react.dev/learn/react-compiler) do it): structural sharing keeps the data's identity across a refetch that changed nothing, so they skip re-rendering.

## Navigations

A navigation is not a background fetch: it's a transition. Use the router's pending state for the navigation itself:

- [TanStack Start](https://raf-query.katt.dev/docs/tanstack-start.md#pending-ui): `pendingComponent` / `pendingMs`, or `useRouterState({ select: (state) => state.isLoading })`
- [Next.js](https://raf-query.katt.dev/docs/react-server-components.md#pending-ui): `useLinkStatus` from `next/link`

`isFetching` covers what happens after the page has rendered.

## useIsFetching

`useIsFetching()` counts the queries with a request in flight, across the whole cache, for one global indicator:

```tsx
function GlobalProgress() {
	const fetching = useIsFetching();
	return fetching > 0 ? <ProgressBar /> : null;
}
```

Pass a target to count only some queries: a factory, a group, or a tag.

```tsx
const loadingComments = useIsFetching(commentsQuery);
```

Outside React, `isFetching(queryClient, target?)` returns the same number.

Refetch the queries below, together or one at a time, and watch both indicators:

> [!NOTE]
> The [web version of this page](https://raf-query.katt.dev/docs/background-fetching-indicators) has an interactive demo here. Its code: [fetching-indicators.tsx](https://github.com/KATT/raf-query/blob/main/www/src/demos/fetching-indicators.tsx).

## Avoiding flashes

On a fast connection, a background refetch can finish in 50 ms, and a spinner that flashes for that long is more distracting than helpful. Delay it with CSS, so it only appears for slower requests:

```css
.spinner {
	animation: fade-in 0s 300ms both;
}

@keyframes fade-in {
	from {
		opacity: 0;
	}
}
```
