Skip to content

Cache

λ Query provides a rich API for directly manipulating the cache. You can perform optimistic mutations, hydrate data from SSR, invalidate stale entries, and inspect the current cache state.

Use mutate() to optimistically update cached data without a network request. This is commonly used after a user action where you know the new value without needing to refetch:

// Set a specific value.
await query.mutate("/api/user", { name: "Erik", email: "[email protected]" });

By default, mutated values expire immediately (0ms), meaning they’ll be revalidated on the next query. Provide an expiration to keep them fresh:

await query.mutate("/api/user", updatedUser, {
expiration(item) {
return 5000; // Fresh for 5 seconds.
},
});

Pass a function to compute the new value from the previous one:

// Append an item to a list.
await query.mutate("/api/posts", function (previous) {
return [...previous, newPost];
});
// The function also receives the current expiration date.
await query.mutate("/api/posts", function (previous, expiresAt) {
console.log("Current expiration:", expiresAt);
return [...previous, newPost];
});

The mutation function can be async. This is the most efficient way to perform mutations because it correctly triggers transition events, enabling the React isMutating state:

await query.mutate("/api/posts", async function (previous) {
const newPost = await createPost({ title: "Hello World" });
return [...previous, newPost];
});

The mutation lifecycle fires two events:

  1. mutating — with the promise (allows UI to show loading state).
  2. mutated — with the resolved value (UI updates to the new data).

Use hydrate() to populate the cache with data you already have, such as data from server-side rendering:

// Hydrate a single key.
query.hydrate("/api/user", userData);
// Hydrate with a specific expiration.
query.hydrate("/api/user", userData, {
expiration(item) {
return 30_000; // Fresh for 30 seconds.
},
});
// Hydrate multiple keys with the same value.
query.hydrate(["/api/post/1", "/api/post/2"], defaultPost);

hydrate() is synchronous and does not trigger a fetch. By default, hydrated values have an expiration of 0ms (immediately expired), meaning they’ll be revalidated on first use. Provide an expiration function to keep them fresh.

A common pattern for SSR is to fetch data on the server, serialize it, and hydrate the client cache before React renders:

// Server: fetch data and embed in HTML.
const user = await fetchUser();
const html = `
<script>window.__DATA__ = ${JSON.stringify({ user })}</script>
<div id="root">${renderToString(<App />)}</div>
`;
// Client: hydrate before rendering.
const query = createQuery();
query.hydrate("/api/user", window.__DATA__.user, {
expiration: () => 30_000,
});
createRoot(document.getElementById("root")).render(
<QueryProvider query={query}>
<App />
</QueryProvider>
);

Use forget() to remove items from the cache. This triggers a forgotten event and causes any active useQuery hooks to refetch (when clearOnForget is enabled):

// Forget a single key.
await query.forget("/api/user");
// Forget multiple keys.
await query.forget(["/api/user", "/api/posts"]);
// Forget all keys matching a pattern.
await query.forget(/^\/api\/posts/);
// Forget everything.
await query.forget();

forget() returns a promise to ensure events have been emitted before it resolves. It removes items from the items cache only — in-flight resolvers continue running (use abort() to cancel those).

Use a regular expression to invalidate groups of related keys:

// Forget all post-related cache entries.
await query.forget(/^\/api\/posts/);
// Forget all user-related entries.
await query.forget(/\/api\/user/);

This is especially useful after a mutation that affects multiple cache entries — for example, creating a new post should invalidate the posts list and any paginated post queries.

List all currently cached keys:

const itemKeys = query.keys("items"); // Resolved items.
const resolverKeys = query.keys("resolvers"); // In-flight requests.

Check when a specific item expires:

const expiresAt = query.expiration("/api/user");
if (expiresAt && expiresAt < new Date()) {
console.log("User data is stale");
}

Read the current cached value without triggering a fetch:

const user = await query.snapshot("/api/user");
if (user) {
console.log("Cached user:", user);
}

Returns undefined if the key is not in the items cache.

For advanced use cases, access the underlying cache instances directly:

const { items, resolvers } = query.caches();
// items is a Cache<ItemsCacheItem>
// resolvers is a Cache<ResolversCacheItem>