Cache Invalidation
When building dynamic applications, keeping the data displayed to the user up-to-date is crucial. Data can become stale for many reasons - another user might have updated it, a background process changed it, or the current user performed an action that invalidates the existing view. Hyper-fetch provides powerful and flexible cache invalidation mechanisms to ensure your app's data is always fresh.
- What cache invalidation is and why it's essential.
- How to invalidate the cache using
client.cache.invalidatewith request instances, strings, and RegExp. - How to batch-invalidate multiple cache entries in a single call.
- How to read, update, and delete cache entries directly.
- How invalidation triggers automatic re-fetching in React hooks.
The Basics
Cache invalidation marks cached data as stale by setting its staleTime to 0. The next consumer - whether a React
useFetch hook or a manual send() call with caching enabled - will see the data is out of date and trigger a fresh
request to the server.
Every cached request is identified by a unique cacheKey, which is derived from the request's method, endpoint, and
query parameters (e.g. GET_/users or GET_/users?page=2). Invalidation works by targeting these keys.
Invalidating by Request, String, or RegExp
The client.cache.invalidate method accepts three different argument types, giving you flexible control over what gets
invalidated:
import { client, getUsers, getUser } from "./api";
// 1. Pass a request instance - uses the request's cacheKey and scope
client.cache.invalidate(getUsers);
// 2. Pass a cacheKey string directly
client.cache.invalidate("GET_/users");
// 3. Pass a RegExp to match multiple cache entries at once
client.cache.invalidate(new RegExp("GET_/users"));
- After a mutation (create, update, delete) to refresh related lists.
- When you need to force fresh data from the server for a specific resource.
- When external events (e.g. websocket notifications) indicate stale data.
- When you need to bulk-invalidate a family of related endpoints (e.g. all paginated pages).
Invalidation After Mutations
The most common pattern is invalidating a list after adding, updating, or deleting an item. Here is a practical example:
const getUsers = client.createRequest()({
endpoint: "/users",
method: "GET",
});
const addUser = client.createRequest<{
response: { id: number; name: string };
payload: { name: string };
}>()({
endpoint: "/users",
method: "POST",
});
// 1. Fetch and cache the users list
const { data } = await getUsers.send();
console.log("Initial users list:", data);
// 2. Add a new user
const { success } = await addUser.send({
payload: { name: "New User" },
});
if (success) {
console.log("New user added successfully.");
// 3. Invalidate the list so hooks will re-fetch automatically
client.cache.invalidate(getUsers);
}
Batch Invalidation
You can invalidate multiple cache entries in a single call by passing an array. The array can mix request instances, strings, and RegExp patterns:
// Invalidate several related endpoints at once
client.cache.invalidate([
getUsers,
getUser.setParams({ userId: 1 }),
new RegExp("GET_/users/\\d+/posts"),
]);
This is useful after a complex mutation that affects multiple resources.
Pattern Matching with RegExp
Sometimes you need to invalidate multiple cache entries that follow a pattern. For instance, you might have cached data
for individual items (/users/1, /users/2) and a list of all items (/users). A RegExp is perfect for this.
const getArticles = client.createRequest()({
method: "GET",
endpoint: "/articles",
});
const getArticle = client.createRequest()({
method: "GET",
endpoint: "/articles/:id",
});
When a single article is updated, you can invalidate the entire family of article caches:
// This will invalidate every cache entry whose key matches the pattern:
// 1. The main list: GET_/articles
// 2. Individual items: GET_/articles/1, GET_/articles/2
// 3. Paginated lists: GET_/articles?page=1, GET_/articles?page=2
client.cache.invalidate(new RegExp("GET_/articles"));
Reading, Updating, and Deleting Cache
Beyond invalidation, the cache provides direct access methods for advanced scenarios like optimistic updates or debugging:
Reading Cache
const cachedData = client.cache.get(getUsers.cacheKey);
if (cachedData) {
console.log("Cached response:", cachedData.data);
console.log("Cached at:", cachedData.responseTimestamp);
}
Updating Cache Directly
Use cache.update for optimistic updates - modifying the cached data without a network request. The update is merged
with the existing cache entry:
// Optimistically add a new user to the cached list
client.cache.update(getUsers, (previousData) => {
if (!previousData) return previousData;
return {
...previousData,
data: [...previousData.data, { id: 999, name: "Optimistic User" }],
};
});
Deleting Cache Entries
Use cache.delete to completely remove an entry from both sync and async storage. Unlike invalidate, which marks
data as stale, delete removes it entirely:
// Remove the cached users list entirely
client.cache.delete(getUsers.cacheKey);
Clearing All Cache
// Remove all cached data
await client.cache.clear();
How Invalidation Triggers Re-fetching
When you call client.cache.invalidate, it emits an invalidation event for each matching cacheKey. React hooks like
useFetch listen for these events and automatically dispatch a fresh request when the data they depend on is
invalidated. This means you don't need to manually re-fetch after invalidating - the hooks handle it for you.
In plain TypeScript (without React hooks), invalidation only marks data as stale. You would need to call send() again
to actually re-fetch the data.
You've learned how to handle cache invalidation in Hyper-fetch!
- You can invalidate cache using
client.cache.invalidatewith request instances, strings, or RegExp. - You can batch-invalidate multiple entries by passing an array.
- You can read, update, and delete cache entries directly for advanced use cases.
- You understand how invalidation automatically triggers re-fetching in React hooks.
