UNPKG

@ai-growth/nextjs

Version:

Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering

251 lines (250 loc) 8.32 kB
import { useEffect, useState, useCallback, useMemo } from 'react'; import { fetchContentBySlug, fetchContentById } from '../utils'; const contentCache = new Map(); /** * React hook for fetching CMS content by slug with comprehensive state management */ export function useCmsContent(slug, options = {}) { const { contentType = 'post', initialData = null, enabled = true, staleTime = 5 * 60 * 1000, // 5 minutes cacheTime = 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus = false, refetchOnReconnect = true, onSuccess, onError, ...fetchOptions } = options; const [content, setContent] = useState(initialData); const [isLoading, setIsLoading] = useState(!initialData && enabled && !!slug); const [isError, setIsError] = useState(false); const [error, setError] = useState(null); const [isFetching, setIsFetching] = useState(false); // Generate cache key const cacheKey = useMemo(() => { if (!slug) return null; return `${contentType}:${slug}:${JSON.stringify(fetchOptions)}`; }, [slug, contentType, fetchOptions]); // Check if cached data is stale const isStale = useMemo(() => { if (!cacheKey) return false; const cached = contentCache.get(cacheKey); if (!cached) return true; return Date.now() - cached.timestamp > cached.staleTime; }, [cacheKey, content]); // Fetch content function const fetchContent = useCallback(async () => { if (!slug || !enabled || !cacheKey) return; // Check cache first const cached = contentCache.get(cacheKey); if (cached && !isStale && !isFetching) { setContent(cached.data); setIsLoading(false); setIsError(false); setError(null); return; } try { setIsFetching(true); setIsError(false); setError(null); // Use the appropriate content fetching function const data = await fetchContentBySlug(contentType, slug, fetchOptions); if (data) { // Cache the result contentCache.set(cacheKey, { data, timestamp: Date.now(), staleTime, }); setContent(data); setIsLoading(false); onSuccess?.(data); } else { throw new Error(`Content not found for slug: ${slug}`); } } catch (err) { const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); setIsError(true); setContent(null); setIsLoading(false); onError?.(errorObj); } finally { setIsFetching(false); } }, [ slug, enabled, cacheKey, contentType, fetchOptions, staleTime, isStale, isFetching, onSuccess, onError, ]); // Refetch function for manual refresh const refetch = useCallback(async () => { if (cacheKey) { contentCache.delete(cacheKey); // Clear cache } await fetchContent(); }, [cacheKey, fetchContent]); // Reset function to clear state const reset = useCallback(() => { setContent(initialData); setIsLoading(!initialData && enabled && !!slug); setIsError(false); setError(null); setIsFetching(false); }, [initialData, enabled, slug]); // Initial fetch effect useEffect(() => { if (slug && enabled) { fetchContent(); } }, [fetchContent]); // Window focus refetch useEffect(() => { if (!refetchOnWindowFocus) return; const handleFocus = () => { if (slug && enabled && isStale) { fetchContent(); } }; window.addEventListener('focus', handleFocus); return () => window.removeEventListener('focus', handleFocus); }, [refetchOnWindowFocus, slug, enabled, isStale, fetchContent]); // Online/reconnect refetch useEffect(() => { if (!refetchOnReconnect) return; const handleOnline = () => { if (slug && enabled && isStale) { fetchContent(); } }; window.addEventListener('online', handleOnline); return () => window.removeEventListener('online', handleOnline); }, [refetchOnReconnect, slug, enabled, isStale, fetchContent]); // Cache cleanup useEffect(() => { const cleanup = () => { const now = Date.now(); for (const [key, entry] of contentCache.entries()) { if (now - entry.timestamp > cacheTime) { contentCache.delete(key); } } }; const interval = setInterval(cleanup, cacheTime); return () => clearInterval(interval); }, [cacheTime]); return { content, isLoading, isError, error, isFetching, isStale, refetch, reset, }; } /** * React hook for fetching CMS content by ID */ export function useCmsContentById(id, options = {}) { const { initialData = null, enabled = true, staleTime = 5 * 60 * 1000, onSuccess, onError, ...fetchOptions } = options; const [content, setContent] = useState(initialData); const [isLoading, setIsLoading] = useState(!initialData && enabled && !!id); const [isError, setIsError] = useState(false); const [error, setError] = useState(null); const [isFetching, setIsFetching] = useState(false); const cacheKey = useMemo(() => { if (!id) return null; return `id:${id}:${JSON.stringify(fetchOptions)}`; }, [id, fetchOptions]); const isStale = useMemo(() => { if (!cacheKey) return false; const cached = contentCache.get(cacheKey); if (!cached) return true; return Date.now() - cached.timestamp > cached.staleTime; }, [cacheKey, content, staleTime]); const fetchContent = useCallback(async () => { if (!id || !enabled || !cacheKey) return; const cached = contentCache.get(cacheKey); if (cached && !isStale && !isFetching) { setContent(cached.data); setIsLoading(false); setIsError(false); setError(null); return; } try { setIsFetching(true); setIsError(false); setError(null); const data = await fetchContentById(id, fetchOptions); if (data) { contentCache.set(cacheKey, { data, timestamp: Date.now(), staleTime, }); setContent(data); setIsLoading(false); onSuccess?.(data); } else { throw new Error(`Content not found for ID: ${id}`); } } catch (err) { const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); setIsError(true); setContent(null); setIsLoading(false); onError?.(errorObj); } finally { setIsFetching(false); } }, [id, enabled, cacheKey, fetchOptions, staleTime, isStale, isFetching, onSuccess, onError]); const refetch = useCallback(async () => { if (cacheKey) { contentCache.delete(cacheKey); } await fetchContent(); }, [cacheKey, fetchContent]); const reset = useCallback(() => { setContent(initialData); setIsLoading(!initialData && enabled && !!id); setIsError(false); setError(null); setIsFetching(false); }, [initialData, enabled, id]); useEffect(() => { if (id && enabled) { fetchContent(); } }, [fetchContent]); return { content, isLoading, isError, error, isFetching, isStale, refetch, reset, }; }