Skip to content

Instantly share code, notes, and snippets.

@nhc
Last active June 20, 2025 07:21
Show Gist options
  • Select an option

  • Save nhc/8466cdceb82f28be157196b170697e9d to your computer and use it in GitHub Desktop.

Select an option

Save nhc/8466cdceb82f28be157196b170697e9d to your computer and use it in GitHub Desktop.
useApiCache
import { useState, useEffect, useRef } from 'react';

// Simple in-memory cache
const cache = new Map();

function useApiCache(key, fetcher, options = {}) {
  const { 
    staleTime = 5 * 60 * 1000, // 5 minutes default
    cacheTime = 10 * 60 * 1000, // 10 minutes default
    refetchOnWindowFocus = false 
  } = options;

  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const abortControllerRef = useRef(null);

  const fetchData = async (forceRefresh = false) => {
    const cached = cache.get(key);
    const now = Date.now();

    // Return fresh cache if available and not stale
    if (!forceRefresh && cached && (now - cached.timestamp) < staleTime) {
      setData(cached.data);
      setError(null);
      return;
    }

    setIsLoading(true);
    setError(null);

    // Cancel previous request
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    abortControllerRef.current = new AbortController();

    try {
      const result = await fetcher({ signal: abortControllerRef.current.signal });
      
      // Cache the result
      cache.set(key, {
        data: result,
        timestamp: now
      });

      setData(result);
    } catch (err) {
      if (err.name !== 'AbortError') {
        setError(err);
      }
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchData();

    // Cleanup cache entries older than cacheTime
    const cleanup = () => {
      const now = Date.now();
      for (const [cacheKey, value] of cache.entries()) {
        if (now - value.timestamp > cacheTime) {
          cache.delete(cacheKey);
        }
      }
    };

    const interval = setInterval(cleanup, cacheTime);
    return () => clearInterval(interval);
  }, [key]);

  // Optional: refetch on window focus
  useEffect(() => {
    if (!refetchOnWindowFocus) return;

    const handleFocus = () => fetchData();
    window.addEventListener('focus', handleFocus);
    return () => window.removeEventListener('focus', handleFocus);
  }, [refetchOnWindowFocus]);

  return {
    data,
    isLoading,
    error,
    refetch: () => fetchData(true),
    invalidate: () => cache.delete(key)
  };
}

Usage

function CallingComponent({ userId }) {
  const { data: user, isLoading, error, refetch } = useApiCache(
    `user-${userId}`,
    async ({ signal }) => {
      return await BaseAPI.get('/domain-sales-endpoint');
    },
    { staleTime: 2 * 60 * 1000 } // 2 minutes
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <h1>{user?.name}</h1>
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment