# Background fetching indicators

> Show that a query is refreshing with isFetching. Prefer the router's pending UI for navigations; useIsFetching stays for compatibility when you need a global count.

A query's first load suspends, so Suspense fallbacks cover it. Later requests (refetches on focus, invalidation) happen in the background, while the old data stays on screen. Read `isFetching` on that query's controls. 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 controls is `true` while any request for the query is in flight, first loads included:

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

Controls are tracked: only components that read `isFetching` re-render when it changes. A parent that just passes the promise down stays put during background refetches.

## 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

You probably don't need this. Prefer the router's pending UI for navigations ([TanStack Start](https://raf-query.katt.dev/docs/tanstack-start.md#pending-ui), [Next.js](https://raf-query.katt.dev/docs/react-server-components.md#pending-ui)) and per-query `isFetching` for background refetches. The hook stays for TanStack Query compatibility when you want one count of every in-flight request across the app:

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

Pass a filter to count only some queries:

```ts
const savingComments = useIsFetching({ queryKey: ["comments"] });
```

It's always `0` on the server and during hydration, so the indicator never appears in server HTML. Outside React, `qc.isFetching(filter?)` 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;
	}
}
```
