In urql, what do the four requestPolicy values do, and when would a storefront screen pick cache-and-network over the default?
answer
- four ways to use the cache
- default prefers what is stored
- show cached, refresh behind
- fetching false while stale true
- per client, per hook, per call
basics
~10 surql's requestPolicy is cache-first by default, returning a cached result or fetching once. cache-and-network returns the cached result marked stale and refetches, network-only always fetches, and cache-only never sends a request.
solid answer
~50 s`requestPolicy` tells the cache exchange how to treat a query. `cache-first`, the default, returns a stored result and only sends a request on a miss. `cache-and-network` returns the stored result immediately with `stale: true` and also sends a request, so the screen updates when fresh data lands; with nothing cached it behaves like a normal fetch. `network-only` always sends a request, and the result still replaces the cached entry for later readers. `cache-only` never sends a request and yields an empty result on a miss. You set it on `createClient`, per `useQuery` call, or per re-run with `reexecuteQuery({ requestPolicy: 'network-only' })`. A storefront picks `cache-and-network` where data changes behind the user's back, such as stock levels on a product page: the page renders instantly from cache and corrects itself, and `stale` drives a subtle 'updating' hint because `fetching` stays `false`.
code
tsx · 19 linesimport { useQuery } from 'urql';
function StockBadge({ productId }: { productId: string }) {
const [{ data, fetching, stale }, reexecuteQuery] = useQuery({
query: ProductStockQuery,
variables: { productId },
requestPolicy: 'cache-and-network',
});
if (fetching) return <span>Checking stock…</span>;
return (
<span aria-busy={stale}>
{data?.product.inStock ? 'In stock' : 'Sold out'}
<button onClick={() => reexecuteQuery({ requestPolicy: 'network-only' })}>
Refresh
</button>
</span>
);
}go deeper
Know the four names and that cache-first is the default. Be able to say which one always hits the network and which one never does.
Explain what each value does on a hit and on a miss, why network-only still fills the cache, and how fetching and stale differ under cache-and-network.
Pick a policy per screen and justify it by how fast the data changes and what a stale value costs; mention requestPolicyExchange or refocusExchange for time-based refreshes.
Treat policies as a product decision: agree which screens may show stale data and for how long, and keep invalidation separate from freshness instead of defaulting to network-only.
## What a request policy controls In urql every query becomes an **operation** that passes through the client's exchanges. The **request policy** is a field on that operation's context, and it tells the caching exchange whether it may answer from its store, must forward the operation to the network, or both. The default `cacheExchange` (the **document cache**, which stores one result per query-and-variables key) and Graphcache's normalized cache both read it. You can set it at three levels, each overriding the one before: 1. **On the client**: `createClient({ url, exchanges, requestPolicy: 'cache-and-network' })` changes the default for every query. 2. **On a hook**: `useQuery({ query, variables, requestPolicy: 'network-only' })` applies to that component's query. 3. **On a re-run**: `reexecuteQuery({ requestPolicy: 'network-only' })`, the second element `useQuery` returns, runs the query once more with a different policy, which is the usual way to build a refresh button. ## The four values | Policy | Result cached | Nothing cached | Sends a request? | |---|---|---|---| | `cache-first` (default) | returns it | fetches | only on a miss | | `cache-and-network` | returns it with `stale: true`, then the fresh one | fetches | always | | `network-only` | ignores it | fetches | always | | `cache-only` | returns it | returns an empty result | never | Two details are easy to miss: - A `network-only` result is still **written to the cache** on the way back. A later `cache-first` reader of the same query gets that fresh result without another request. - `cache-only` treats a miss as an empty result, not as an error, so the component renders with no `data` and no `error`. ## cache-and-network, fetching and stale `useQuery` exposes two flags that people conflate: - **`fetching`** is `true` only while the hook has no result for the current request yet, for instance on the very first load. - **`stale`** is `true` when the result on screen is known to be outdated or a newer request for it is in flight. Under `cache-and-network` with a cached result, the first render gets the cached data, `fetching: false` and `stale: true`. A spinner keyed to `fetching` therefore never shows, which is the point: the page is usable at once. When the network answer arrives, the hook re-renders with the fresh data and `stale: false`. If you want a small 'refreshing' indicator, key it to `stale`. ## Choosing per screen in a storefront - **Category and product lists** that change a few times a day suit `cache-first`: back-navigation is instant and costs no request. - **A product page showing stock or price** suits `cache-and-network`: the shopper sees the page immediately, and a sold-out size corrects itself within a round trip. - **The checkout summary** before payment suits `network-only`: a stale total there is a correctness problem, not a cosmetic one. - **An offline banner or a prefetched preview** can use `cache-only`, reading what earlier screens already loaded without triggering traffic. ## Refreshing without hand-picking policies Two optional exchanges upgrade policies for you, and both work by switching an operation to `cache-and-network`: - `requestPolicyExchange` from `@urql/exchange-request-policy` upgrades a `cache-first` query once its last network result is older than a `ttl`, five minutes by default, and accepts a `shouldUpgrade` function to limit which operations it touches. - `refocusExchange` from `@urql/exchange-refocus` re-runs active queries with `cache-and-network` when the page becomes visible again. They sit in the `exchanges` array before the cache, so the cache sees the upgraded policy. ## Common mistakes with request policies - **Expecting a spinner on refresh.** Under `cache-and-network` the refresh runs with `fetching: false`; a loading state keyed only to `fetching` never appears for it, by design. - **Reading never-loaded data with `cache-only`.** The screen renders blank, with neither `data` nor `error`, because a miss is not an error. - **Making `network-only` the client default.** Every screen then skips cache reads, so back-navigation always waits on the network. - **Treating `cache-and-network` as polling.** It refreshes once per request for the query, not on a timer; a live stock counter needs a subscription or a refresh trigger. ## Where the policy stops A request policy decides how one query uses the cache. It does not decide when cached results become wrong after a mutation; that is the cache exchange's invalidation logic, which differs between the document cache and Graphcache. Choosing `network-only` everywhere to avoid thinking about invalidation throws away the cache and doubles traffic on every navigation.
- A developer switches every query to network-only to 'avoid stale data'. What does that cost?Every mount and every back-navigation now sends a request and shows a loading state, although results are still written to the cache. The cache is reduced to a write-only log. Stale data after a mutation is an invalidation problem; the document cache's `__typename` invalidation, `additionalTypenames` or Graphcache updaters address it without disabling reads.
- How do you make urql refresh a product list in the background if it was last fetched more than a minute ago?Add `requestPolicyExchange({ ttl: 60_000 })` from `@urql/exchange-request-policy` before the cache exchange. When a query is re-requested after its `ttl` has passed, the exchange upgrades its policy to `cache-and-network`, so the cached list renders at once and a fresh one follows. The default `ttl` is five minutes.
saying these in an interview costs you the question
- urql's default requestPolicy is cache-and-network, revalidating on every read.
- network-only results bypass the cache and are never stored.
- cache-and-network shows a loading spinner because fetching is true.
- cache-only throws an error when nothing is cached.
- requestPolicy can only be set once, on createClient.