🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

TanStack Query Guide 2026: Reagieren Sie auf Datenabruf, Mutationen und Caching

⏱️5 min read  ·  929 words

TanStack Query (ehemals React Query) ist der Goldstandard für die Serverstatusverwaltung in React-, Vue-, Solid- und Angular-Anwendungen im Jahr 2026. Es eliminiert die Komplexität des manuellen Datenabrufs, Cachings und der Synchronisierung und ersetzt Hunderte von Zeilen useEffect-Code durch einfache, leistungsstarke Hooks.

Warum TanStack Query?

  • Kein Boilerplate– ersetzt Redux + fetch + useEffect-Muster
  • Automatisches Caching– Daten werden nach Schlüssel zwischengespeichert und bei Bedarf aktualisiert
  • Erneutes Abrufen im Hintergrund— Veraltete Daten werden sofort angezeigt, neue Daten im Hintergrund
  • Lade-/Fehlerzustände– integriert, keine manuelle Statusverwaltung
  • Optimistische Updates– UI-Updates vor Bestätigung durch den Server
  • DevTools– Cache prüfen, erneut abrufen, aus Browser-Erweiterung ungültig machen

Installation und Einrichtung

npm install @tanstack/react-query @tanstack/react-query-devtools

# Optional: axios for HTTP
npm install axios

// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,    // 5 minutes (data considered fresh)
      gcTime: 10 * 60 * 1000,      // 10 minutes (cache garbage collection)
      retry: 2,                     // retry failed requests twice
      refetchOnWindowFocus: true,   // refetch when tab becomes active
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

useQuery – Daten abrufen

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

interface User {
  id: number;
  name: string;
  email: string;
}

// Query function
async function fetchUser(id: number): Promise<User> {
  const { data } = await axios.get(`/api/users/${id}`);
  return data;
}

// Component using query
function UserProfile({ userId }: { userId: number }) {
  const {
    data: user,
    isLoading,
    isError,
    error,
    isFetching,        // true when refetching in background
    isStale,           // true when data is stale
    refetch,           // manually trigger refetch
  } = useQuery({
    queryKey: ['users', userId],    // unique cache key
    queryFn: () => fetchUser(userId),
    enabled: userId > 0,            // only fetch when userId is valid
    staleTime: 60_000,              // fresh for 1 minute
    select: (data) => data,         // transform data before returning
  });

  if (isLoading) return <Skeleton />;
  if (isError) return <ErrorBoundary error={error} />;

  return (
    <div>
      {isFetching && <small>Refreshing...</small>}
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  );
}

Parallele Abfragen

// Fetch multiple queries simultaneously
function Dashboard() {
  const [userQuery, postsQuery, statsQuery] = useQueries({
    queries: [
      { queryKey: ['user', 1], queryFn: () => fetchUser(1) },
      { queryKey: ['posts'], queryFn: fetchPosts },
      { queryKey: ['stats'], queryFn: fetchStats },
    ],
  });

  if (userQuery.isLoading || postsQuery.isLoading) return <Loading />;

  return (
    <div>
      <UserCard user={userQuery.data} />
      <PostList posts={postsQuery.data} />
      <StatsPanel stats={statsQuery.data} />
    </div>
  );
}

useMutation – Erstellen, Aktualisieren, Löschen

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

async function createPost(post: { title: string; content: string }) {
  const { data } = await axios.post('/api/posts', post);
  return data;
}

function NewPostForm() {
  const queryClient = useQueryClient();

  const { mutate, isPending, isError, error } = useMutation({
    mutationFn: createPost,

    // Called when mutation succeeds
    onSuccess: (newPost) => {
      // Option 1: Invalidate cache (triggers refetch)
      queryClient.invalidateQueries({ queryKey: ['posts'] });

      // Option 2: Update cache directly (no refetch)
      queryClient.setQueryData(['posts'], (old: Post[]) => [...old, newPost]);

      // Show toast notification
      toast.success('Post created!');
    },

    onError: (error) => {
      toast.error(`Failed: ${error.message}`);
    },
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    mutate({
      title: (form.title as HTMLInputElement).value,
      content: (form.content as HTMLTextAreaElement).value,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" required placeholder="Title" />
      <textarea name="content" required placeholder="Content" />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Creating...' : 'Create Post'}
      </button>
      {isError && <p className="error">{error.message}</p>}
    </form>
  );
}

Optimistische Updates

const { mutate: likePost } = useMutation({
  mutationFn: (postId: number) => axios.post(`/api/posts/${postId}/like`),

  onMutate: async (postId) => {
    // Cancel any outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['posts', postId] });

    // Snapshot the previous value
    const previousPost = queryClient.getQueryData(['posts', postId]);

    // Optimistically update the UI
    queryClient.setQueryData(['posts', postId], (old: Post) => ({
      ...old,
      likes: old.likes + 1,
    }));

    // Return context for rollback
    return { previousPost };
  },

  // Roll back on error
  onError: (error, postId, context) => {
    queryClient.setQueryData(['posts', postId], context?.previousPost);
    toast.error('Failed to like post');
  },

  // Always refetch after mutation
  onSettled: (data, error, postId) => {
    queryClient.invalidateQueries({ queryKey: ['posts', postId] });
  },
});

Unendliche Abfragen

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

function InfinitePostList() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['posts', 'infinite'],
    queryFn: ({ pageParam = 1 }) => fetchPosts(pageParam),
    initialPageParam: 1,
    getNextPageParam: (lastPage, pages) =>
      lastPage.hasMore ? pages.length + 1 : undefined,
  });

  return (
    <div>
      {data?.pages.map((page, i) => (
        <React.Fragment key={i}>
          {page.posts.map(post => <PostCard key={post.id} post={post} />)}
        </React.Fragment>
      ))}
      <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
        {isFetchingNextPage ? 'Loading...' : hasNextPage ? 'Load More' : 'All loaded'}
      </button>
    </div>
  );
}

Benutzerdefinierte Abfrage-Hooks

// hooks/useUsers.ts — reusable query hooks
export function useUsers(filters?: UserFilters) {
  return useQuery({
    queryKey: ['users', filters],
    queryFn: () => fetchUsers(filters),
    select: (data) => ({
      users: data.users,
      total: data.total,
    }),
  });
}

export function useUser(id: number) {
  const queryClient = useQueryClient();
  return useQuery({
    queryKey: ['users', id],
    queryFn: () => fetchUser(id),
    // Pre-populate from list query
    initialData: () => {
      const usersQuery = queryClient.getQueryData<UsersResponse>(['users']);
      return usersQuery?.users.find(u => u.id === id);
    },
    initialDataUpdatedAt: () =>
      queryClient.getQueryState(['users'])?.dataUpdatedAt,
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (user: Partial<User> & { id: number }) =>
      axios.patch(`/api/users/${user.id}`, user).then(r => r.data),
    onSuccess: (updated) => {
      queryClient.setQueryData(['users', updated.id], updated);
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

TanStack Query im Jahr 2026 eliminiert 80 % des Boilerplates im typischen React-Datenabrufcode. Beginnen Sie mit useQuery für Lesevorgänge und useMutation für Schreibvorgänge. Verwenden Sie invalidateQueries für einfache Cache-Aktualisierungen und setQueryData für optimistische Aktualisierungen. Die Lerninvestition zahlt sich bei jedem Projekt aus.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা