CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-swr

React Hooks library for remote data fetching with stale-while-revalidate caching strategy

Pending
Overview
Eval results
Files

cache-management.mddocs/

Cache Management

SWR provides powerful cache management capabilities through global mutate functions and preloading utilities for programmatic cache manipulation and data optimization.

Capabilities

Global Mutate Function

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
  }
);

Preload Function

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);
};

Cache Inspection and Management

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>
  );
}

Advanced Mutation Patterns

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
  }
};

Cache Warming Strategies

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]);

Cache Invalidation Strategies

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 minute

Event-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);
    }
  }
};

Performance Optimization

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 state

Install with Tessl CLI

npx tessl i tessl/npm-swr

docs

cache-management.md

core-data-fetching.md

global-configuration.md

immutable-data.md

index.md

infinite-loading.md

mutations.md

subscriptions.md

tile.json