Files
2026-02-19 22:18:01 +00:00

68 lines
1.7 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
export type RequestStatus = "idle" | "loading" | "success" | "error";
export type RequestState<T> = {
status: RequestStatus;
data: T | null;
error: Error | null;
};
type UseRequestOptions = {
successDuration?: number;
};
export function useRequest<TArgs extends unknown[], TData>(
fn: (...args: TArgs) => Promise<TData>,
options: UseRequestOptions = {},
) {
const { successDuration = 1500 } = options;
const [state, setState] = useState<RequestState<TData>>({
status: "idle",
data: null,
error: null,
});
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const run = useCallback(
async (...args: TArgs) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setState({ status: "loading", data: null, error: null });
try {
const data = await fn(...args);
setState({ status: "success", data, error: null });
if (successDuration > 0) {
timeoutRef.current = setTimeout(() => {
setState((prev) => ({ ...prev, status: "idle" }));
}, successDuration);
}
return data;
} catch (error) {
const normalized = error instanceof Error ? error : new Error("Request failed");
setState({ status: "error", data: null, error: normalized });
throw normalized;
}
},
[fn, successDuration],
);
return {
...state,
isLoading: state.status === "loading",
run,
reset: () => setState({ status: "idle", data: null, error: null }),
};
}