Back

TanStack Query für intelligentere Datenabrufe in React verwenden

TanStack Query für intelligentere Datenabrufe in React verwenden

Wenn Sie React-Apps entwickelt haben, die mit APIs kommunizieren, kennen Sie das Muster: useEffect für den Abruf, useState für die Daten, ein weiteres useState für das Laden, vielleicht noch eines für Fehler. Bevor Sie es merken, verwalten Sie ein verworrenes Durcheinander von State, nur um eine Liste von Benutzern anzuzeigen.

Dieser manuelle Ansatz funktioniert, ist aber fragil. Was passiert, wenn der Benutzer wegnavigiert und zurückkommt? Rufen Sie erneut ab? Verwenden Sie veraltete Daten? Was ist mit Wiederholungsversuchen, wenn das Netzwerk ausfällt? Das sind keine Grenzfälle – das ist die Realität von Produktions-Apps.

TanStack Query (früher React Query) löst diese Probleme, indem es Server-State anders als Client-State behandelt. Anstatt imperativ Daten abzurufen, deklarieren Sie, was Sie benötigen, und lassen die Bibliothek Caching, Synchronisation und Updates handhaben. Dieser Artikel zeigt Ihnen, wie Sie von manuellem Datenabruf zu einem intelligenteren, deklarativen Ansatz wechseln, der skaliert.

Wichtige Erkenntnisse

  • Ersetzen Sie manuelle useEffect + useState-Muster durch deklarative useQuery-Hooks für saubereren, wartbareren Code
  • Nutzen Sie automatisches Caching, Hintergrund-Refetching und Request-Deduplizierung zur Verbesserung der Benutzererfahrung
  • Verwenden Sie Mutationen mit optimistischen Updates, um responsive UIs zu erstellen, die sich sofort anfühlen
  • Implementieren Sie geeignete Query-Invalidierungsstrategien, um Daten in Ihrer Anwendung synchron zu halten
  • Vermeiden Sie häufige Fallstricke wie die Verwendung von TanStack Query für lokalen State oder das Vergessen dynamischer Parameter in Query Keys

Das Problem mit manuellem Datenabruf

Hier ist eine typische React-Komponente, die Daten abruft:

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    
    async function fetchUsers() {
      setIsLoading(true);
      setError(null);
      
      try {
        const response = await fetch('/api/users');
        if (!response.ok) throw new Error('Failed to fetch');
        
        const data = await response.json();
        if (!cancelled) {
          setUsers(data);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false);
        }
      }
    }
    
    fetchUsers();
    
    return () => { cancelled = true };
  }, []);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Das funktioniert, aber schauen Sie, was wir verwalten:

  • Drei separate State-Teile
  • Cleanup-Logik zur Verhinderung von State-Updates bei unmountierten Komponenten
  • Kein Caching – jedes Mount löst einen neuen Abruf aus
  • Keine Retry-Logik für fehlgeschlagene Requests
  • Keine Möglichkeit zu wissen, ob unsere Daten veraltet sind

Multiplizieren Sie diese Komplexität mit Dutzenden von Komponenten. Hier kommt TanStack Query ins Spiel.

Wie TanStack Query Datenabruf intelligenter macht

TanStack Query behandelt Server-State als erstklassigen Bürger. Anstatt manuell Abrufe zu orchestrieren, beschreiben Sie Ihre Datenanforderungen deklarativ:

import { useQuery } from '@tanstack/react-query';

function UserList() {
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => {
      if (!res.ok) throw new Error('Failed to fetch');
      return res.json();
    }),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Gleiche Funktionalität, aber beachten Sie, was fehlt:

  • Kein useState für Daten, Laden oder Fehler
  • Kein useEffect oder Cleanup-Logik
  • Keine manuelle State-Synchronisation

Aber hier ist, was Sie gewinnen:

  • Automatisches Caching: Navigieren Sie weg und zurück, sehen Sie sofortige Ergebnisse
  • Hintergrund-Refetching: Veraltete Daten werden stillschweigend aktualisiert
  • Request-Deduplizierung: Mehrere Komponenten können dieselbe Query teilen
  • Eingebaute Retry-Logik: Fehlgeschlagene Requests werden automatisch wiederholt
  • Optimistische Updates: UI-Updates vor Server-Bestätigung

Kernkonzepte für intelligenteres Datenmanagement

Queries: Server-State lesen

Queries rufen Daten ab und cachen sie. Der useQuery-Hook akzeptiert ein Konfigurationsobjekt mit zwei wesentlichen Eigenschaften:

const { data, isLoading, error } = useQuery({
  queryKey: ['todos', userId, { status: 'active' }],
  queryFn: () => fetchTodosByUser(userId, { status: 'active' }),
  staleTime: 5 * 60 * 1000, // 5 Minuten
  gcTime: 10 * 60 * 1000, // 10 Minuten (früher cacheTime)
});

Query Keys identifizieren jede Query eindeutig. Sie sind Arrays, die enthalten können:

  • Statische Identifikatoren: ['users']
  • Dynamische Parameter: ['user', userId]
  • Komplexe Filter: ['todos', { status, page }]

Wenn sich ein Teil des Query Keys ändert, weiß TanStack Query, dass frische Daten abgerufen werden müssen.

Mutationen: Server-State ändern

Während Queries Daten lesen, modifizieren Mutationen sie:

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateTodo() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: (newTodo) => 
      fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
        headers: { 'Content-Type': 'application/json' },
      }).then(res => {
        if (!res.ok) throw new Error('Failed to create todo');
        return res.json();
      }),
    onSuccess: () => {
      // Invalidieren und neu abrufen
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  return (
    <button 
      onClick={() => mutation.mutate({ title: 'New Todo' })}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? 'Creating...' : 'Create Todo'}
    </button>
  );
}

Mutationen handhaben den kompletten Lebenszyklus: Ladezustände, Fehlerbehandlung und Erfolgs-Callbacks. Der onSuccess-Callback ist perfekt für die Aktualisierung Ihres Caches nach Änderungen.

Query-Invalidierung: Daten frisch halten

Invalidierung markiert Queries als veraltet und löst Hintergrund-Refetches aus:

// Alles invalidieren
queryClient.invalidateQueries();

// Spezifische Queries invalidieren
queryClient.invalidateQueries({ queryKey: ['todos'] });

// Mit exakter Übereinstimmung invalidieren
queryClient.invalidateQueries({ 
  queryKey: ['todo', 5], 
  exact: true 
});

So hält TanStack Query Ihre UI nach Mutationen synchron. Ein Todo ändern? Die Todos-Liste invalidieren. Einen Benutzer aktualisieren? Die Daten dieses Benutzers invalidieren.

TanStack Query in Ihrer React-App einrichten

Installieren Sie zunächst das Paket:

npm install @tanstack/react-query

Dann umhüllen Sie Ihre App mit dem QueryClient-Provider:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1 Minute
      gcTime: 5 * 60 * 1000, // 5 Minuten (früher cacheTime)
      retry: 3,
      retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

Der QueryClient verwaltet das gesamte Caching und die Synchronisation. Normalerweise erstellen Sie eine Instanz für Ihre gesamte App.

Erweiterte Muster für Produktions-Apps

Optimistische Updates

Aktualisieren Sie die UI sofort und synchronisieren Sie dann mit dem Server:

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // Ausgehende Refetches abbrechen
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    
    // Vorherigen Wert speichern
    const previousTodos = queryClient.getQueryData(['todos']);
    
    // Optimistisch aktualisieren
    queryClient.setQueryData(['todos'], old => 
      old ? [...old, newTodo] : [newTodo]
    );
    
    // Kontext für Rollback zurückgeben
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    // Bei Fehler zurückrollen
    if (context?.previousTodos) {
      queryClient.setQueryData(['todos'], context.previousTodos);
    }
  },
  onSettled: () => {
    // Immer nach Fehler oder Erfolg neu abrufen
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

Abhängige Queries

Verketten Sie Queries, die voneinander abhängen:

const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: () => getUserByEmail(email),
});

const { data: projects } = useQuery({
  queryKey: ['projects', user?.id],
  queryFn: () => getProjectsByUser(user.id),
  enabled: !!user?.id, // Nur ausführen, wenn Benutzer-ID existiert
});

Infinite Queries

Handhaben Sie paginierte Daten mit unendlichem Scrollen:

import { useInfiniteQuery } from '@tanstack/react-query';

const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
} = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam = 0 }) => fetchProjects({ page: pageParam }),
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  initialPageParam: 0,
});

Häufige Fallstricke und Lösungen

Den QueryClientProvider vergessen

Wenn Ihre Queries undefined zurückgeben oder überhaupt nicht funktionieren, prüfen Sie, ob Ihre App mit QueryClientProvider umhüllt ist. Das ist der häufigste Setup-Fehler.

TanStack Query für lokalen State verwenden

TanStack Query ist für Server-State gedacht. Verwenden Sie es nicht für Formulareingaben, UI-Toggles oder anderen rein clientseitigen State. Bleiben Sie bei useState oder useReducer für diese.

Statische Query Keys mit dynamischen Daten

Schließen Sie immer dynamische Parameter in Ihre Query Keys ein:

// ❌ Falsch - zeigt dieselben Daten für alle Benutzer
useQuery({
  queryKey: ['user'],
  queryFn: () => fetchUser(userId),
});

// ✅ Richtig - eindeutiger Cache-Eintrag pro Benutzer
useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

Fazit

TanStack Query verwandelt React-Datenabruf von einem manuellen, fehleranfälligen Prozess in ein deklaratives, robustes System. Indem es Server-State als grundlegend verschieden von Client-State behandelt, eliminiert es Boilerplate und fügt gleichzeitig mächtige Features wie Caching, Synchronisation und optimistische Updates hinzu.

Die mentale Umstellung ist einfach: Hören Sie auf zu denken, wie Sie Daten abrufen, und fangen Sie an zu denken, welche Daten Sie benötigen. Deklarieren Sie Ihre Anforderungen durch Query Keys und Funktionen und lassen Sie TanStack Query die Komplexität der Synchronisation handhaben.

Fangen Sie klein an – ersetzen Sie einen useEffect-Abruf durch useQuery. Sobald Sie die sofortigen Ladezustände, automatischen Wiederholungen und nahtloses Caching in Aktion sehen, werden Sie verstehen, warum TanStack Query für moderne React-Anwendungen unverzichtbar geworden ist.

FAQs

Es ist dieselbe Bibliothek. React Query wurde in TanStack Query umbenannt, um zu reflektieren, dass sie jetzt mehrere Frameworks jenseits von React unterstützt. Das React-spezifische Paket wird weiterhin gepflegt und verwendet dieselbe API, die Sie kennen.

Ja, aber Sie brauchen es wahrscheinlich nicht für Server-State. TanStack Query handhabt Caching und das Teilen von Daten zwischen Komponenten automatisch. Verwenden Sie Redux oder Context für echten Client-State wie Benutzereinstellungen oder UI-State, und TanStack Query für alles, was von einer API kommt.

Standardmäßig rufen Queries bei Fensterfokus, Wiederverbindung und Mount neu ab. Sie steuern die Frische mit staleTime (wie lange Daten frisch bleiben) und gcTime (wie lange ungenutzte Daten behalten werden). Veraltete Queries werden im Hintergrund neu abgerufen, während gecachte Daten sofort angezeigt werden.

Invalidierung markiert Queries als veraltet, ruft aber nicht sofort neu ab – sie wartet darauf, dass die Query wieder verwendet wird. Direktes Refetching löst eine sofortige Netzwerkanfrage aus. Verwenden Sie Invalidierung nach Mutationen für bessere Performance, da sie nur Queries neu abruft, die tatsächlich angezeigt werden.

TanStack Query eignet sich hervorragend für nahezu Echtzeit-Updates durch Polling und Refetch-Intervalle. Für echte Echtzeit-Bedürfnisse kombinieren Sie es mit WebSockets: Verwenden Sie den Socket für Live-Updates und TanStack Query für das initiale Laden von Daten und als Fallback, wenn der Socket getrennt wird.

Ja. TanStack Query 5+ ist vollständig kompatibel mit React 19. Die hier verwendeten APIs (Queries, Mutationen, Invalidierung) sind unverändert, sodass die Beispiele in diesem Artikel ohne Änderungen funktionieren werden.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers