Skip to content
fearchitect
State & Data

Server State & Data Fetching

Async, shared, remote-owned data that requires a dedicated cache layer.

By Abas TurabliReviewed

Summary

Server state — API responses, database records — is async, shared across components, and has an expiry. TanStack Query and SWR manage it with a client-side cache keyed by query keys, background revalidation, and request deduplication. React Server Components shift some of this work to the server entirely, removing the need for a client cache layer in those cases.

Jump to the interview angle

Server state differs from client state (UI toggles, form fields) in four ways: it's async (requires a network round-trip), shared (multiple components observe the same data), remote-owned (the server can change it without the client knowing), and potentially stale (a cached copy drifts from the truth over time). TanStack Query v5 and SWR give each piece of server state a query key — a serialisable identifier — and store the response in an in-process cache. They track loading/error/success so components don't have to, and revalidate in the background automatically.

Server state vs client state

  • **Async** — server state always involves a network fetch; client state (modals, form values) is synchronous.
  • **Shared** — multiple components read the same cache slot; changing it anywhere propagates everywhere.
  • **Remote-owned** — the server mutates it independently; the client only holds a snapshot.
  • **Stale-capable** — cached data drifts without explicit revalidation; `staleTime` controls the freshness window.
  • **RSC alternative** — React Server Components fetch on the server and ship HTML; no client cache needed for that data.

TanStack Query v5 — useQuery (object syntax)

v5 dropped the positional overload. Every call takes a single options object. queryKey is the cache identity; staleTime sets the freshness window in ms.

useQuery with staleTime (TanStack Query v5)tsx
import { useQuery } from "@tanstack/react-query";

interface Post { id: number; title: string; }

async function fetchPost(id: number): Promise<Post> {
  const res = await fetch(`/api/posts/${id}`);
  if (!res.ok) throw new Error("fetch failed");
  return res.json();
}

export function PostDetail({ id }: { id: number }) {
  const { data, isPending, isError } = useQuery({
    queryKey: ["post", id],   // cache key — changes when id changes
    queryFn: () => fetchPost(id),
    staleTime: 30_000,        // fresh for 30 s; no background refetch while fresh
  });

  if (isPending) return <p>Loading…</p>;
  if (isError)   return <p>Error loading post.</p>;
  return <h1>{data.title}</h1>;
}

Object-syntax useQuery (v5 required). staleTime: 30_000 prevents redundant refetches for 30 seconds. Changing id creates a new cache entry automatically.

Two components share one cache slot; after staleTime expires, focus triggers a silent background refetch that updates both.

RSC fetch is not the same as client-cache fetch

In Next.js App Router, identical fetch calls within one render are deduped (request memoization, on by default). The Data Cache — persisting results across requests — is off by default; opt in with { next: { revalidate } } or { cache: 'force-cache' }. Fetching the same resource in both an RSC and a client useQuery without coordination sends duplicate requests.

Interview angle

Interviewers test whether you distinguish server state from UI state. Name the four properties (async, shared, remote-owned, stale-capable) and explain query keys as the cache identity. Contrast client-cache libraries with RSC fetch.

Soundbite: "Server state is remote-owned and stale by default — a dedicated cache layer beats manual useEffect wiring every time."

Key terms

query key
Serialisable array that identifies a cache entry in TanStack Query or SWR; changing it triggers a new fetch.
staleTime
Duration in ms during which cached data is considered fresh and no background refetch fires.
gcTime
How long an unused TanStack Query cache entry is kept in memory before garbage collection. Default 5 min.
background revalidation
Refetch triggered silently on focus or reconnect; updates the cache without a loading spinner.
request deduplication
Multiple components calling `useQuery` with the same key share one in-flight network request.

Further reading

Search fearchitect

Jump to a topic, mode, or action.