Run an async function and expose loading, error, and data state. No external data library needed.
import { useState, useEffect, useCallback } from 'react';
export function useAsync<T>(
asyncFn: () => Promise<T>,
immediate = true
): {
loading: boolean;
data: T | null;
error: Error | null;
run: () => Promise<void>;
} {
const [state, setState] = useState<{
loading: boolean;
data: T | null;
error: Error | null;
}>({ loading: immediate, data: null, error: null });
const run = useCallback(() => {
setState((s) => ({ ...s, loading: true, error: null }));
return Promise.resolve(asyncFn())
.then((data) => setState({ loading: false, data, error: null }))
.catch((error) => setState({ loading: false, data: null, error: error instanceof Error ? error : new Error(String(error)) }));
}, [asyncFn]);
useEffect(() => {
if (immediate) run();
}, [immediate, run]);
return { ...state, run };
}Pass an async function (e.g. () => fetch(url).then(r => r.json())). Returns { loading, data, error, run }.
Set immediate: false to not run on mount; call run() when needed (e.g. on submit).
const { loading, data, run } = useAsync(() => fetchUser(id), false);