Code Snippets
/

React useFetch Hook with Cancellation

React useFetch Hook with Cancellation

Building a useFetch from scratch teaches the pieces every data-fetching library has to solve: tracking loading and error state, cancelling in-flight requests when the URL changes, and ignoring stale responses after the component unmounts. This snippet covers the canonical AbortController-based hook, a generation-counter variant that protects against race conditions when AbortController is unavailable, and a mutate refetch helper for manual revalidation.

JavaScript
Hard
react
hooks
code-template
js-fetch-api

1,018 views

31

function useFetch(url, options) {
    const [state, setState] = useState({ data: null, error: null, loading: true });
    useEffect(() => {
        if (!url) { setState({ data: null, error: null, loading: false }); return undefined; }
        const controller = new AbortController();
        setState({ data: null, error: null, loading: true });
        fetch(url, { ...(options || {}), signal: controller.signal })
            .then(async (res) => {
                if (!res.ok) throw new Error(`HTTP ${res.status}`);
                return res.json();
            })
            .then((data) => setState({ data, error: null, loading: false }))
            .catch((err) => {
                if (err.name === 'AbortError') return;
                setState({ data: null, error: err, loading: false });
            });
        return () => controller.abort();
    }, [url]);
    return state;
}

function useState(v) { return [v, () => {}]; }
function useEffect(fn) { fn(); }
const result = useFetch(null);
console.log('null url short-circuit:', result);

The canonical pattern stores { data, error, loading } in one state object so renders never see a half-updated combination. Each effect cycle creates a fresh AbortController, passes its signal to fetch, and aborts the request from the cleanup function when the URL changes or the component unmounts. The AbortError branch in the catch is critical: an aborted fetch rejects with that error, and treating it as a real failure would flash an error state during navigation. Skipping the request when url is falsy lets parents drive lazy fetching with useFetch(shouldLoad ? url : null).

2 more snippets in this entry are available for premium members.

Upgrade to Premium