When building a React app with Vite and a REST API, you quickly need a way to fetch and manage server data. You will soon have to choose between writing native fetch calls inside a useEffect hook or installing a dedicated data-fetching library like TanStack React Query.

  • How native fetch works in React with useEffect hooks
  • What TanStack React Query is and why developers use it
  • The performance and developer experience trade-offs of both options
  • A clear recommendation for your next project

Using Native fetch with useEffect

Native fetch is built directly into modern browsers, meaning you do not need to install any extra packages. A REST API is a way for your frontend code to talk to a backend server using standard web protocols.

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    fetch('https://api.example.com/user')
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, []);

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

This approach keeps your initial bundle size small because you rely entirely on built-in browser features. However, it requires writing a lot of boilerplate code to handle loading states, errors, and caching yourself.

Using TanStack React Query

React Query is a popular library that handles caching, background updates, and request deduplication out of the box. Caching means storing fetched data in memory so your app does not need to download the exact same data twice.

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

function UserProfile() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user'],
    queryFn: () => fetch('https://api.example.com/user').then(res => res.json())
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error loading user</p>;
  return <div>Hello, {data.name}</div>
}

This approach writes significantly less boilerplate and automatically retries failed requests. The main downside is that it adds to your JavaScript bundle size and requires learning a new API.

Things to watch out for

A common mistake with native fetch is forgetting to handle race conditions when components unmount quickly. Another frequent trap is disabling React Query's built-in caching globally instead of configuring it per query where needed.

If you are building a simple portfolio or a weekend project, stick to native fetch to keep things lightweight. For any production app with multiple authenticated pages, install React Query today and save yourself hours of writing custom cache logic.