DEV Community

Alex Spinov
Alex Spinov

Posted on

TanStack Query Has a Free Data Fetching Library That Replaces useEffect

Every React developer has written useEffect + fetch + useState + loading + error for the hundredth time. TanStack Query replaces all of that with a single hook that handles caching, background refetching, pagination, optimistic updates, and more.

What TanStack Query Gives You for Free

  • Smart caching — data is cached and shared between components
  • Background refetching — stale data updates automatically
  • Pagination/infinite scroll — built-in helpers
  • Optimistic updates — instant UI feedback before server confirms
  • Retry logic — automatic retries with exponential backoff
  • DevTools — visual query inspector
  • Framework agnostic — React, Vue, Solid, Svelte, Angular

Quick Start

npm install @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode
// app.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

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

Fetching Data (Replace useEffect Forever)

Before (useEffect):

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    fetch('/api/users')
      .then(res => res.json())
      .then(data => { if (!cancelled) { setUsers(data); setLoading(false); } })
      .catch(err => { if (!cancelled) { setError(err); setLoading(false); } });
    return () => { cancelled = true; };
  }, []);
  // Still missing: caching, refetching, retry, deduplication...
}
Enter fullscreen mode Exit fullscreen mode

After (TanStack Query):

function UserList() {
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => res.json()),
  });
  // Gets: caching, refetching, retry, deduplication, devtools — FOR FREE
}
Enter fullscreen mode Exit fullscreen mode

Mutations (Create/Update/Delete)

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

  const mutation = useMutation({
    mutationFn: (newUser) => fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(newUser),
    }).then(res => res.json()),

    onSuccess: () => {
      // Invalidate and refetch the users list
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });

  return (
    <button onClick={() => mutation.mutate({ name: 'Alice', email: 'a@b.com' })}>
      {mutation.isPending ? 'Creating...' : 'Create User'}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Infinite Scroll

function Feed() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: ({ pageParam }) => fetch(`/api/posts?cursor=${pageParam}`).then(r => r.json()),
    initialPageParam: '',
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });

  return (
    <div>
      {data?.pages.map(page =>
        page.items.map(post => <PostCard key={post.id} post={post} />)
      )}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Optimistic Updates

const toggleTodo = useMutation({
  mutationFn: (id) => fetch(`/api/todos/${id}/toggle`, { method: 'PATCH' }),

  onMutate: async (id) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    const previous = queryClient.getQueryData(['todos']);

    // Optimistically update
    queryClient.setQueryData(['todos'], (old) =>
      old.map(t => t.id === id ? { ...t, done: !t.done } : t)
    );

    return { previous };
  },

  onError: (err, id, context) => {
    // Rollback on error
    queryClient.setQueryData(['todos'], context.previous);
  },
});
Enter fullscreen mode Exit fullscreen mode

The Verdict

TanStack Query turns data fetching from a solved-every-time problem into a solved-once problem. Caching, refetching, pagination, optimistic updates — all handled. Stop writing useEffect for data fetching.


Need help building production web scrapers or data pipelines? I build custom solutions. Reach out: spinov001@gmail.com

Check out my awesome-web-scraping collection — 400+ tools for extracting web data.

Top comments (0)