68 lines
1.7 KiB
TypeScript
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 }),
|
|
};
|
|
}
|