ttl
Description: Defines how long an entry should be kept in the cache. Use -1 for endless caching.
Default Value: 900000 (15 minutes).
- How To: Adjust
ttl(in milliseconds) based on data volatility. Use-1for static data (like enums or config) that should persist in cache indefinitely. For data that changes infrequently, a longerttl(e.g., 1 hour) is appropriate.// Correct: Static data that never expiresuseFetch('app-config',fetchAppConfig,{ ttl: -1 } // Never expires);// Correct: Data valid for 1 houruseFetch('hourly-report',generateHourlyReport,{ ttl: 60 * 60 * 1000 } // removed from cache after 1 hour); - Best Practice: Avoid setting
ttltoo low (e.g., 5 seconds) for expensive operations that take a long time to generate, as it will lead to frequent re-generation. Conversely, do not usettl: -1for frequently changing data, as it will never update unless manually refetched or invalidated.// Avoid this: Regenerates expensive report every 5 seconds!useFetch('quarterly-report',() => generateQuarterlyReport(), // Takes 10 seconds{ ttl: 5000 });// Avoid this: Notifications will never update automaticallyuseFetch('notifications',() => fetchNotifications(),{ ttl: -1 } // Never updates!});
Deep Dive: How ttl works step by step
Example:
function UserProfile({ userId }) {
const { data } = useFetch(
['user', userId], // The fetch key
() => fetchUserProfile(userId), // The action function
{ ttl: 5 * 60 * 1000 } // Options
);
return <ProfileDisplay user={data} />;
}
What happens step by step with the timeline:
-
t=0s: Component mounts with
userId="123"- Cache is empty
- Fetch executes
- Data cached with key
['user', '123'] - Cache entry expires at:
t=0s + 5min = t=5min
-
t=2min: User navigates away
- Component unmounts
- Cache entry still exists (TTL not expired yet)
-
t=3min: User navigates back
- Component mounts again with same
userId="123" - Cache checked: Data still valid (3min < 5min TTL)
- No fetch executed → Instant display from cache
- Component mounts again with same
-
t=6min: User navigates back again
- Component mounts with same
userId="123" - Cache checked: Data expired (6min > 5min TTL)
- Cache entry marked as stale or removed
- New fetch executes → Fresh data loaded
- Component mounts with same
-
t=6min: Cache cleanup runs (happens every 60 seconds by default)
- DataClient scans all cache entries.
- Finds entries with
expiresAt < currentTime. - Removes expired entries: Physical removal from memory happens if the cache size exceeds the "soft limit" (default 50 entries), unless
enduring: trueis set.
Logical vs. Physical Expiration: Even if the cleanup process hasn't run yet, any attempt to access an expired entry will be treated as a cache miss. The TTL ensures logical data freshness immediately; the cleanup process simply handles memory management.
What happens with different TTL values:
// TTL = 10 seconds (very short)
ttl: 10000
// → Data expires quickly
// → Good for: Real-time stock prices, live scores
// → Cache entry marked as stale after 10 seconds (treated as a cache miss) and removed from memory with the next cleanup (runs every 60 seconds)
// TTL = 15 minutes (default)
ttl: 900000
// → Data valid for 15 minutes
// → Good for: User profiles, product details
// → Balances freshness and performance
// TTL = -1 (infinite)
ttl: -1
// → Data NEVER expires on its own
// → Good for: App config, translations, static content
// → Only removed if you manually delete or cache is full (without enduring)
// TTL = 0 (no cache)
ttl: 0
// → Data immediately stale
// → Always refetches on component mount
// → Good for: One-time verification codes (rarely used)
Key insight: TTL controls cache validity on remount. It's different from refetch because refetch controls reactive background updates when a component re-renders.