React Query is a powerful library for data fetching, caching, and synchronization in React applications. It reduces boilerplate code, improves performance, and simplifies state management when dealing with server data.

Why use react query?

Traditional data fetching in React usually involves useEffect, useState, and a lot of manual loading/error handling. React Query abstracts much of this away and introduces a more declarative and scalable approach to working with APIs.

Benefits include:

  • Built-in Caching
  • Automatic Background Refetching
  • Pagination and Infinite Queries
  • Out-of-the-box Devtools for Debugging
  • Better UX with Stale-While-Revalidate Pattern

Basic usage

Here’s how to fetch data from an API using React Query.

First, install the package:

npm install @tanstack/react-query

Set up the Query Client and Provider:

// main.jsx or App.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

Now let’s fetch some data:

// Users.jsx
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

const fetchUsers = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
  return data;
};

export default function Users() {
  const { data, error, isLoading } = useQuery(['users'], fetchUsers);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error fetching users</p>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

This code automatically handles loading, error states, caching, and background updates.

Handling POST, PUT, DELETE (MUTATIONS)

React Query also makes it easy to send data with useMutation.

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

const createUser = async (user) => {
  const { data } = await axios.post('https://jsonplaceholder.typicode.com/users', user);
  return data;
};

export default function CreateUser() {
  const queryClient = useQueryClient();
  const mutation = useMutation(createUser, {
    onSuccess: () => {
      // Refetch users after adding a new one
      queryClient.invalidateQueries(['users']);
    },
  });

  const handleSubmit = () => {
    mutation.mutate({ name: 'New User' });
  };

  return (
    <div>
      <button onClick={handleSubmit}>Create User</button>
      {mutation.isPending && <p>Creating...</p>}
      {mutation.isSuccess && <p>User created!</p>}
    </div>
  );
}

Tips for efficient use

  1. Use Query Keys Wisely

Query keys control cache identity. Use consistent, descriptive keys like ['user', userId].

  1. Set staleTime and cacheTime

Adjust freshness and cache behavior to avoid unnecessary refetches.

  1. Use Selectors

Shape the data returned from queries to fit your UI needs.

useQuery(['users'], fetchUsers, {
  select: data => data.map(user => user.name),
});
  1. Prefetching Data

Anticipate data needs to improve perceived performance.

queryClient.prefetchQuery(['user', userId], () => fetchUser(userId));

Conclusion

React Query streamlines API interaction in React apps by managing caching, refetching, error handling, and background updates—all with less code and better performance. Whether you're building a dashboard, form-heavy app, or just want better UX, React Query is a game-changer for working with APIs.