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