Zruck zum Blog, du Zniachtl
Okt. 2026·2 Min. zum LesnVo da KI gmocht

A eigenen Data-Fetching-Hook in React bauen

#react#javascript#frontend

Jede React-Codebasis sammelt irgendwann a Dutzend fost identische useEffect-Blöcke an, de Daten holn, den Ladezustand tracken und Fehler behandeln. Dieses Muster in oan einzigen wiederverwendbaren Hook auszulagern spart ned nur Tipparbeit - richtig gmocht behebt's a a poar subtile Bugs, de einekopierte useEffect-Fetches fost imma hom.

De naive Version, und wos do falsch is

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data) => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);

  if (loading) return <p>Loading...</p>;
  return <h1>{user.name}</h1>;
}

Des schaut vernünftig aus, hot aber an echten Bug: Wenn si userId schnö ändert (schnelle Navigation zwischen Profilen), san mehrere Fetches gleichzeitig unterwegs, und es gibt ka Garantie, dass de letzte Antwort, de ankummt, zum aktuellen userId passt. Du kannst am End an veraltetn User rendern, weil a oidere, langsamere Anfrage nach ana neicheren, schnelleren fertig worn is.

Den Hook auslagern, mit Abbruch

function useFetch(url) {
  const [state, setState] = useState({ data: null, loading: true, error: null });

  useEffect(() => {
    let cancelled = false;
    setState({ data: null, loading: true, error: null });

    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error(`Request failed: ${res.status}`);
        return res.json();
      })
      .then((data) => {
        if (!cancelled) setState({ data, loading: false, error: null });
      })
      .catch((error) => {
        if (!cancelled) setState({ data: null, loading: false, error });
      });

    return () => {
      cancelled = true;
    };
  }, [url]);

  return state;
}
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Something went wrong.</p>;
  return <h1>{user.name}</h1>;
}

Des cancelled-Flag is de zentrale Reparatur: Wenn si url ändert (oda die Komponente unmountet wird), lauft die Cleanup-Funktion vom Effect und setzt cancelled für den vorherigen Effect-Durchlauf auf true. Wenn der ältere Fetch später fertig wird, prüft sein .then()-Callback des Flag und steigt aus, statt frischeren State mit veraltetn Daten zum überschreibn.

AbortController statt Boolean-Flag valwendn

Des cancelled-Flag valhindert, dass React auf a veraltete Antwort reagiert, aber der Netzwerk-Request söba lauft bis zum Ende weiter und vaschwendet Bandbreite. AbortController bricht den laufenden Request tatsächlich ab:

function useFetch(url) {
  const [state, setState] = useState({ data: null, loading: true, error: null });

  useEffect(() => {
    const controller = new AbortController();
    setState({ data: null, loading: true, error: null });

    fetch(url, { signal: controller.signal })
      .then((res) => {
        if (!res.ok) throw new Error(`Request failed: ${res.status}`);
        return res.json();
      })
      .then((data) => setState({ data, loading: false, error: null }))
      .catch((error) => {
        if (error.name !== 'AbortError') {
          setState({ data: null, loading: false, error });
        }
      });

    return () => controller.abort();
  }, [url]);

  return state;
}

Da Check auf error.name !== 'AbortError' is wichtig: Wenn ma an Fetch obbricht, wird sein Promise rejected, und ohne den Check würd jeder durchs Cleanup ausglöste Abbruch fälschlich an Fehler-State auf ana Komponente setzn, de scho weitergezogen is.

Manuelles Refetch dazubauen

A fixe url-Dependency erledigt des automatische Neuladen, wenn si Props ändern, aber manchmoi brauchst an "Retry"-Button, der die söbe URL neu lodt. Dazu brauchst a Möglichkeit, den Effect anzustoßn, ohne url söba zu ändern:

function useFetch(url) {
  const [state, setState] = useState({ data: null, loading: true, error: null });
  const [refetchIndex, setRefetchIndex] = useState(0);

  useEffect(() => {
    const controller = new AbortController();
    setState({ data: null, loading: true, error: null });

    fetch(url, { signal: controller.signal })
      .then((res) => {
        if (!res.ok) throw new Error(`Request failed: ${res.status}`);
        return res.json();
      })
      .then((data) => setState({ data, loading: false, error: null }))
      .catch((error) => {
        if (error.name !== 'AbortError') {
          setState({ data: null, loading: false, error });
        }
      });

    return () => controller.abort();
  }, [url, refetchIndex]);

  const refetch = () => setRefetchIndex((n) => n + 1);

  return { ...state, refetch };
}

refetchIndex beeinflusst den Request söba ned - es gibt's nur, um a Dependency zum sei, auf de da Effect reagiern kann, und gibt Aufrufern a Möglichkeit, an neichn Durchlauf zu erzwingen, ohne url zu ändern.

Wos dieser Hook no immer ned kann

Die Version is a solider Baustein, aber bewusst koa vollständige Data-Fetching-Lösung. Sie cacht koane Antworten über Komponenten hinweg, de dieselbe URL anfordern, dedupliziert koane gleichzeitigen Anfragen für dieselbe Ressource und versucht's ned nochamoi bei vorübergehenden Fehlern. Genau de Lücke füllen Bibliotheken wia React Query und SWR - sie bauen auf denselben useEffect- + Abbruch-Grundlagen auf, de do gezeigt wern, plus a geteilten Cache und a Request-Deduplizierungs-Schicht obendrauf. Söba amoi a eigene Version zum schreibn lohnt si, um zum vastehn, wos tatsächlich passiert; bei mehr ois a poar datenabhängigen Komponenten in deiner App zu einer dieser Bibliotheken zum greifn, is meistens de richtige Entscheidung.

Fazit

Ein Data-Fetching-Hook is eigentlich a State Machine (idle → loading → success/error), eingpackt um an useEffect, der sorgfältig mit Cleanup umgeht. De Abbruchlogik is ka optionaler Feinschliff - ohne sie erzeugen schnelle Prop-Änderungen still und leise Race Conditions, bei denen a oide Antwort a neiche überschreibt. Pass auf, du Tschapperl!

NeicherDa Trigramm-Algorithmus: Fuzzy Matching ohne ZaubereiElterCSS Container Queries: A praktischer Leitfodn