React Hooks library for remote data fetching with stale-while-revalidate caching strategy
—
SWR provides powerful cache management capabilities through global mutate functions and preloading utilities for programmatic cache manipulation and data optimization.
Global function for programmatic cache manipulation across all SWR instances.
/**
* Global mutate function for cache manipulation
* @param key - Key to mutate, or function to match multiple keys
* @param data - New data, Promise, or function returning new data
* @param options - Mutation options or boolean for revalidate
* @returns Promise resolving to the updated data
*/
function mutate<Data = any>(
key: Key | ((key: Key) => boolean),
data?: Data | Promise<Data> | MutatorCallback<Data>,
options?: boolean | MutatorOptions<Data>
): Promise<Data | undefined>;
// Overload for mutating without providing data (triggers revalidation)
function mutate(
key: Key | ((key: Key) => boolean),
options?: boolean | MutatorOptions
): Promise<any>;Usage Examples:
import { mutate } from "swr";
// Update specific cache entry
await mutate("/api/user", newUserData);
// Revalidate specific key
await mutate("/api/user");
// Update multiple related keys
await mutate((key) => typeof key === "string" && key.startsWith("/api/users"));
// Optimistic update with rollback
await mutate(
"/api/user",
updateUserAPI(newData),
{
optimisticData: { ...currentData, ...newData },
rollbackOnError: true
}
);Function to preload data into cache before it's needed by components.
/**
* Preload data into cache before components need it
* @param key - Cache key for the data
* @param fetcher - Function to fetch the data
* @returns The fetched data
*/
function preload<Data = any>(
key: Key,
fetcher: Fetcher<Data, Key>
): Data;Usage Examples:
import { preload } from "swr";
// Preload critical data on app start
preload("/api/user", fetcher);
preload("/api/config", fetcher);
// Preload on hover (before navigation)
const handleLinkHover = () => {
preload("/api/dashboard", fetcher);
};
// Preload related data after successful mutation
const handleUserUpdate = async (userData) => {
await mutate("/api/user", userData);
// Preload related data that user might need next
preload("/api/user/preferences", fetcher);
preload("/api/user/activity", fetcher);
};Access and manipulate the cache directly for advanced use cases.
import { useSWRConfig } from "swr";
function CacheManager() {
const { cache, mutate } = useSWRConfig();
// Inspect cache contents
const logCache = () => {
console.log("Cache contents:", cache);
// Get all cached keys
const keys = Array.from(cache.keys());
console.log("Cached keys:", keys);
// Get specific cache entry
const userData = cache.get("/api/user");
console.log("User data:", userData);
};
// Clear entire cache
const clearCache = () => {
cache.clear();
};
// Remove specific cache entry
const removeCacheEntry = (key: string) => {
cache.delete(key);
};
// Batch cache operations
const batchCacheUpdate = () => {
const updates = [
["/api/user", newUserData],
["/api/settings", newSettings],
["/api/preferences", newPreferences]
];
updates.forEach(([key, data]) => {
mutate(key, data, false); // Update without revalidation
});
};
return (
<div>
<button onClick={logCache}>Log Cache</button>
<button onClick={clearCache}>Clear Cache</button>
<button onClick={batchCacheUpdate}>Batch Update</button>
</div>
);
}Pattern Matching for Bulk Updates:
// Update all user-related cache entries
await mutate(
(key) => typeof key === "string" && key.includes("/api/user"),
undefined, // No data provided, will revalidate
{ revalidate: true }
);
// Update cache entries matching specific pattern
await mutate(
(key) => {
if (typeof key === "string") {
return key.startsWith("/api/posts") || key.startsWith("/api/comments");
}
return false;
}
);
// Complex key matching with arrays
await mutate(
(key) => {
if (Array.isArray(key)) {
return key[0] === "/api/search" && key[1] === currentQuery;
}
return false;
}
);Conditional Cache Updates:
// Update cache only if data has changed
const updateUserIfChanged = async (newUserData: User) => {
await mutate(
"/api/user",
newUserData,
{
populateCache: (result, currentData) => {
// Only update if data actually changed
if (JSON.stringify(result) !== JSON.stringify(currentData)) {
return result;
}
return currentData; // Keep existing data
},
revalidate: false
}
);
};
// Conditional optimistic updates
const optimisticUpdate = async (action: () => Promise<any>) => {
const shouldBeOptimistic = checkNetworkCondition();
if (shouldBeOptimistic) {
await mutate(
"/api/data",
action(),
{
optimisticData: (current) => ({ ...current, updating: true }),
rollbackOnError: true
}
);
} else {
// Just do the action without optimistic update
await action();
await mutate("/api/data"); // Revalidate after
}
};Application Startup:
// Warm cache with essential data on app load
const warmCache = async () => {
const essentialData = [
["/api/user", userFetcher],
["/api/config", configFetcher],
["/api/permissions", permissionsFetcher]
];
// Preload all essential data in parallel
await Promise.all(
essentialData.map(([key, fetcher]) => preload(key, fetcher))
);
};
// Call during app initialization
warmCache();Predictive Loading:
// Preload data based on user behavior
const handleUserInteraction = (action: string) => {
switch (action) {
case "hover_profile":
preload("/api/user/profile", fetcher);
preload("/api/user/activity", fetcher);
break;
case "hover_dashboard":
preload("/api/dashboard/stats", fetcher);
preload("/api/dashboard/notifications", fetcher);
break;
case "search_focus":
preload("/api/search/recent", fetcher);
preload("/api/search/suggestions", fetcher);
break;
}
};
// Usage in components
<button
onMouseEnter={() => handleUserInteraction("hover_dashboard")}
onClick={navigateToDashboard}
>
Dashboard
</button>Route-Based Preloading:
// Preload data for upcoming routes
const preloadRoute = (routeName: string) => {
const routePreloads = {
dashboard: [
"/api/dashboard/stats",
"/api/dashboard/recent-activity"
],
profile: [
"/api/user/profile",
"/api/user/settings"
],
reports: [
"/api/reports/list",
"/api/reports/filters"
]
};
const keys = routePreloads[routeName] || [];
keys.forEach(key => preload(key, fetcher));
};
// Integration with router
const router = useRouter();
useEffect(() => {
const handleRouteChangeStart = (url: string) => {
const routeName = url.split("/")[1];
preloadRoute(routeName);
};
router.events.on("routeChangeStart", handleRouteChangeStart);
return () => router.events.off("routeChangeStart", handleRouteChangeStart);
}, [router]);Time-Based Invalidation:
// Invalidate cache entries older than threshold
const invalidateStaleCache = () => {
const STALE_TIME = 5 * 60 * 1000; // 5 minutes
const now = Date.now();
mutate((key) => {
// Get cache entry to check timestamp
const entry = cache.get(key);
if (entry && entry.timestamp) {
return now - entry.timestamp > STALE_TIME;
}
return false;
});
};
// Run periodically
setInterval(invalidateStaleCache, 60000); // Every minuteEvent-Based Invalidation:
// Invalidate cache based on external events
const handleExternalEvent = (event: ExternalEvent) => {
switch (event.type) {
case "user_updated":
// Invalidate all user-related data
mutate((key) => typeof key === "string" && key.includes("/api/user"));
break;
case "permissions_changed":
// Invalidate permission-related data
mutate("/api/permissions");
mutate("/api/user/permissions");
break;
case "global_config_changed":
// Invalidate configuration data
mutate((key) => typeof key === "string" && key.includes("/api/config"));
break;
}
};
// Listen for external events (WebSocket, SSE, etc.)
websocket.on("event", handleExternalEvent);Smart Invalidation:
// Intelligent cache invalidation based on data relationships
const smartInvalidate = async (changedData: any, context: string) => {
const invalidationMap = {
user_profile: [
"/api/user",
"/api/user/profile",
(key) => typeof key === "string" && key.startsWith("/api/user/")
],
user_settings: [
"/api/user/settings",
"/api/user/preferences"
],
post_created: [
"/api/posts",
(key) => Array.isArray(key) && key[0] === "/api/posts" && key[1] === "list"
]
};
const toInvalidate = invalidationMap[context] || [];
for (const target of toInvalidate) {
if (typeof target === "function") {
await mutate(target);
} else {
await mutate(target);
}
}
};Batch Cache Operations:
// Batch multiple cache operations for better performance
const batchCacheUpdates = async (updates: Array<[string, any]>) => {
// Disable revalidation for all updates except the last one
const promises = updates.map(([key, data], index) =>
mutate(key, data, { revalidate: index === updates.length - 1 })
);
await Promise.all(promises);
};
// Usage
await batchCacheUpdates([
["/api/user", newUserData],
["/api/settings", newSettings],
["/api/preferences", newPreferences]
]);Memory Management:
// Clean up cache to prevent memory leaks
const cleanupCache = () => {
const { cache } = useSWRConfig();
const MAX_CACHE_SIZE = 100;
if (cache.size > MAX_CACHE_SIZE) {
// Remove oldest entries (this is a simplified example)
const entries = Array.from(cache.entries());
const oldestEntries = entries
.sort((a, b) => (a[1].timestamp || 0) - (b[1].timestamp || 0))
.slice(0, entries.length - MAX_CACHE_SIZE);
oldestEntries.forEach(([key]) => cache.delete(key));
}
};
// Run cleanup periodically
useEffect(() => {
const interval = setInterval(cleanupCache, 60000); // Every minute
return () => clearInterval(interval);
}, []);Cache Debugging:
// Debugging utilities for cache management
const useCacheDebugger = () => {
const { cache, mutate } = useSWRConfig();
const debugCache = {
log: () => {
console.group("SWR Cache Debug");
console.log("Cache size:", cache.size);
console.log("Keys:", Array.from(cache.keys()));
console.groupEnd();
},
logKey: (key: string) => {
const entry = cache.get(key);
console.log(`Cache entry for ${key}:`, entry);
},
stats: () => {
const keys = Array.from(cache.keys());
const typeStats = keys.reduce((acc, key) => {
const type = typeof key === "string" ? "string" :
Array.isArray(key) ? "array" : "object";
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log("Cache statistics:", typeStats);
},
export: () => {
const cacheData = {};
cache.forEach((value, key) => {
cacheData[JSON.stringify(key)] = value;
});
return cacheData;
}
};
return debugCache;
};
// Usage in development
const debugCache = useCacheDebugger();
debugCache.log(); // Log current cache stateInstall with Tessl CLI
npx tessl i tessl/npm-swr