DEV Community

Alex Spinov
Alex Spinov

Posted on

TanStack Query Has a Free Library That Manages Server State — No More useEffect + useState for API Calls

The Data Fetching Problem

Every React developer writes this:

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  fetch('/api/users').then(r => r.json()).then(setData).catch(setError).finally(() => setLoading(false));
}, []);
Enter fullscreen mode Exit fullscreen mode

No caching. No refetching. No deduplication. No optimistic updates.

TanStack Query handles all of this. One hook. Built-in cache. Automatic refetching.

What TanStack Query Gives You

useQuery — Replace useEffect

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

function Users() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Enter fullscreen mode Exit fullscreen mode

Cached. Deduped. Auto-refetches on window focus. Background updates.

useMutation — Forms and Actions

const mutation = useMutation({
  mutationFn: (newUser) => fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(newUser),
  }),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

<button onClick={() => mutation.mutate({ name: 'John' })}>
  {mutation.isPending ? 'Creating...' : 'Create User'}
</button>
Enter fullscreen mode Exit fullscreen mode

Optimistic Updates

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    const previous = queryClient.getQueryData(['todos']);
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
    return { previous };
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previous);
  },
});
Enter fullscreen mode Exit fullscreen mode

UI updates instantly. Rolls back if the server fails.

Infinite Scroll

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam = 0 }) => fetch(`/api/posts?page=${pageParam}`).then(r => r.json()),
  getNextPageParam: (lastPage) => lastPage.nextCursor,
});
Enter fullscreen mode Exit fullscreen mode

Works With Any Framework

React, Vue, Solid, Svelte, Angular — same API, different adapter.

Quick Start

npm install @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode

Why This Matters

Server state (data from APIs) is fundamentally different from client state (UI state). TanStack Query is purpose-built for server state — caching, synchronization, and background updates.


Fetching web data for your queries? Check out my web scraping actors on Apify Store — structured API data. For custom solutions, email spinov001@gmail.com.

Top comments (0)