Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Data fetching libraries

The useFetch hook from the custom hooks lesson solves a simple task. A real app needs more: do not load the same post twice, refresh data when the user returns to the tab, retry a failed request. Writing all of that by hand takes long, so data fetching libraries are used. They keep responses in a cache — memory that returns data immediately, without a new request.

LibraryWhat sets it apart
TanStack QueryThe most widespread, works with any way of fetching
SWRA similar idea with a smaller API
RTK QueryBuilt into Redux Toolkit

Below is an example with TanStack Query (version 5).

Installing and connecting

npm install @tanstack/react-query

The app is wrapped in a provider with a query client — in src/main.jsx:

// src/main.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// imports of StrictMode, createRoot, BrowserRouter and App stay as before
 
const queryClient = new QueryClient();
 
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </QueryClientProvider>
  </StrictMode>,
);

QueryClient holds the cache of all the app's queries.

A query with useQuery

Let us rewrite the post list — src/components/PostList.jsx:

// src/components/PostList.jsx
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router';
 
async function fetchPosts() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  if (!response.ok) throw new Error(`Error ${response.status}`);
  return response.json();
}
 
export default function PostList() {
  const { data, isPending, error } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
  });
 
  if (isPending) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;
 
  return (
    <ul>
      {data.map((post) => (
        <li key={post.id}>
          <Link to={`/posts/${post.id}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  );
}

queryFn is a function that loads the data and throws an error on failure. queryKey is the key under which the response is stored in the cache. Open a post and go back to the home page: the list appears at once, without "Loading…" — it comes from the cache and is quietly refreshed in the background.

A key with a parameter

If a query depends on a parameter, the parameter goes into the key:

// src/pages/PostPage.jsx
const { id } = useParams();
const { data: post, isPending } = useQuery({
  queryKey: ['posts', id],
  queryFn: () =>
    fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then((r) => r.json()),
});

Each post gets its own cache entry: ['posts', '1'], ['posts', '2']. Going back to a post you already opened shows it instantly.

What else the library does

FeatureWhat it is called
Sending data to the serveruseMutation
Refreshing the cache after a changequeryClient.invalidateQueries
Retrying a failed requestOn by default, 3 attempts

Common mistake

If a query parameter is missing from the key, the library treats all requests as one and shows the wrong data:

// Wrong: every post has the same key.
useQuery({ queryKey: ['post'], queryFn: () => fetchPost(id) });
 
// Correct:
useQuery({ queryKey: ['post', id], queryFn: () => fetchPost(id) });

Everything queryFn depends on must be in queryKey.

Thanks for reading!

Donate to the site — we work hard for you.