Blum / frontend /lib /progressiveFetch.ts
Italianhype's picture
Add Learning dashboard performance architecture
91e0f22 verified
Raw
History Blame Contribute Delete
1.67 kB
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
export function useProgressiveFetch<T>(loader: () => Promise<T>, options: { timeoutMs?: number; immediate?: boolean } = {}) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(Boolean(options.immediate));
const [error, setError] = useState("");
const [updatedAt, setUpdatedAt] = useState<string | null>(null);
const abortRef = useRef(false);
const run = useCallback(async () => {
abortRef.current = false;
setLoading(true);
setError("");
let timeout: ReturnType<typeof setTimeout> | null = null;
try {
const timeoutPromise = new Promise<never>((_, reject) => {
timeout = setTimeout(() => reject(new Error("Request timed out")), options.timeoutMs ?? 12000);
});
const value = await Promise.race([loader(), timeoutPromise]);
if (!abortRef.current) {
setData(value);
setUpdatedAt(new Date().toISOString());
}
} catch (err) {
if (!abortRef.current) setError(err instanceof Error ? err.message : String(err));
} finally {
if (timeout) clearTimeout(timeout);
if (!abortRef.current) setLoading(false);
}
}, [loader, options.timeoutMs]);
useEffect(() => {
if (options.immediate) run();
return () => {
abortRef.current = true;
};
}, [options.immediate, run]);
return { data, loading, error, updatedAt, retry: run };
}
export function useVisibilityFetch<T>(loader: () => Promise<T>, enabled: boolean, options: { timeoutMs?: number } = {}) {
return useProgressiveFetch(loader, { ...options, immediate: enabled });
}