React Hooks in SPFx: useCancelableFetch

React Hooks in SPFx: useCancelableFetch

Build a small, reusable hook around fetch and AbortController to keep your SPFx components safe from updates after unmount or rapid property changes.

API

import { useEffect, useState } from 'react';

export function useCancelableFetch<T>(url: string, deps: any[] = []) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const ac = new AbortController();
    (async () => {
      try {
        setLoading(true);
        setError(null);
        const res = await fetch(url, { signal: ac.signal });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json = await res.json();
        setData(json as T);
      } catch (e: any) {
        if (e.name !== 'AbortError') setError(e.message \u0000?? 'Unknown error');
      } finally {
        setLoading(false);
      }
    })();
    return () => ac.abort();
  }, deps);

  return { data, loading, error };
}

Usage (SharePoint REST)

type TasksResponse = { value: { id: number; title: string }[] };
const { data, loading, error } = useCancelableFetch<TasksResponse>(
  `${siteUrl}/_api/web/lists/getbytitle('Tasks')/items`,
  [siteUrl]
);

if (loading) return <Spinner />;
if (error) return <MessageBar intent="error">{error}</MessageBar>;
return <ItemsList items={data?.value ?? []} />;

Usage (Microsoft Graph)

type UsersResponse = { value: { id: string; displayName: string }[] };
const { data, loading, error } = useCancelableFetch<UsersResponse>(
  `${graphBase}/v1.0/users?$top=20`,
  [graphBase]
);

Notes

  • Always include dependencies like siteUrl in the deps array.
  • Handles cancellation on unmount and between rapid prop changes.
  • Keep the hook focused; for pagination or revalidation, extend with parameters or compose another hook.

See also

  • /blog/reactspfx/01_hooks_01_useState
  • /blog/reactspfx/01_hooks_02_useEffect
  • /blog/reactspfx/01_hooks_03_useRef
  • /blog/reactspfx/01_hooks_04_useContext
  • /blog/reactspfx/01_hooks_05_useReducer