Skip to main content

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 -1 for static data (like enums or config) that should persist in cache indefinitely. For data that changes infrequently, a longer ttl (e.g., 1 hour) is appropriate.
    // Correct: Static data that never expires
    useFetch(
    'app-config',
    fetchAppConfig,
    { ttl: -1 } // Never expires
    );

    // Correct: Data valid for 1 hour
    useFetch(
    'hourly-report',
    generateHourlyReport,
    { ttl: 60 * 60 * 1000 } // removed from cache after 1 hour
    );
  • Best Practice: Avoid setting ttl too 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 use ttl: -1 for 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 automatically
    useFetch(
    '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:

  1. 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
  2. t=2min: User navigates away

    • Component unmounts
    • Cache entry still exists (TTL not expired yet)
  3. 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
  4. 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
  5. 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: true is 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.