How to Master Data Fetching in React Using TanStack Query

How to Master Data Fetching in React Using TanStack Query

Data Fetching in React Using TanStack Query

Data fetching is a critical aspect of modern web applications, and managing it efficiently can be challenging. Enter TanStack Query (formerly known as React Query), a powerful library that simplifies data fetching, caching, synchronization, and server-state management in React applications. In this article, we’ll explore how TanStack Query can revolutionize your data-fetching workflow and provide practical examples to get you started.


What is TanStack Query?

TanStack Query is a data-fetching library for React that provides a declarative and intuitive API for managing server state. It handles caching, background updates, stale data, pagination, and more, allowing developers to focus on building features rather than managing complex data-fetching logic.

Key Features:

  1. Automatic Caching: TanStack Query caches data automatically and intelligently refetches it when needed.

  2. Background Updates: It keeps your UI up-to-date by refetching data in the background.

  3. Devtools: Built-in devtools help you debug and inspect queries.

  4. Pagination and Infinite Queries: Easily handle paginated or infinite data.

  5. Optimistic Updates: Update the UI optimistically before the server responds.

  6. TypeScript Support: First-class TypeScript support for type-safe development.


Why Use TanStack Query?

Traditional data-fetching approaches in React often involve writing repetitive boilerplate code for fetching, caching, and updating data. TanStack Query abstracts away these complexities, providing a streamlined and efficient way to manage server state. Here’s why you should consider using it:

  • Reduced Boilerplate: No need to write custom hooks or manage loading/error states manually.

  • Improved Performance: Intelligent caching and background updates ensure optimal performance.

  • Better Developer Experience: Built-in tools and a simple API make development faster and more enjoyable.

  • Scalability: Designed to handle complex data-fetching scenarios in large applications.


Getting Started with TanStack Query

Installation:

To get started, install TanStack Query and its dev tools (optional but recommended):

npm install @tanstack/react-query @tanstack/react-query-devtools

Setting Up the Query Client:

Wrap your application with the QueryClientProvider and create a QueryClient instance.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* Your application components */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

Basic Data Fetching with TanStack Query

TanStack Query uses queries to fetch data. A query is a declarative dependency on an asynchronous data source. Here’s how to fetch data from an API:

Example: Fetching a List of Posts

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

const fetchPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
};

function Posts() {
  const { data, isLoading, isError, error } = useQuery(['posts'], fetchPosts);

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error: {error.message}</div>;

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

Explanation:

  • useQuery: A hook that fetches and manages the data.

  • Query Key: The ['posts'] array is a unique identifier for the query. It can be used for caching and refetching.

  • States: isLoading, isError, and error provide information about the query’s status.


Advanced Features

1. Pagination

TanStack Query makes it easy to handle paginated data using the useQuery hook.

const fetchPosts = async (page) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
  return response.json();
};

function Posts() {
  const [page, setPage] = useState(1);
  const { data, isLoading } = useQuery(['posts', page], () => fetchPosts(page));

  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <>
          <ul>
            {data.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </ul>
          <button onClick={() => setPage((prev) => prev - 1)} disabled={page === 1}>
            Previous
          </button>
          <button onClick={() => setPage((prev) => prev + 1)}>Next</button>
        </>
      )}
    </div>
  );
}

2. Mutations (Updating Data)

TanStack Query uses mutations to update server data. For example, creating a new post:

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

const createPost = async (newPost) => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    body: JSON.stringify(newPost),
  });
  return response.json();
};

function AddPost() {
  const queryClient = useQueryClient();
  const mutation = useMutation(createPost, {
    onSuccess: () => {
      queryClient.invalidateQueries(['posts']); // Refetch posts after mutation
    },
  });

  const handleSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const newPost = { title: formData.get('title'), body: formData.get('body') };
    mutation.mutate(newPost);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Title" />
      <textarea name="body" placeholder="Body" />
      <button type="submit">Add Post</button>
    </form>
  );
}

3. Infinite Queries

For infinite loading scenarios, use the useInfiniteQuery hook.

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

const fetchPosts = async ({ pageParam = 1 }) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}&_limit=10`);
  return response.json();
};

function Posts() {
  const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery(
    ['posts'],
    fetchPosts,
    {
      getNextPageParam: (lastPage, allPages) => allPages.length + 1,
    }
  );

  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <>
          <ul>
            {data.pages.map((page) =>
              page.map((post) => <li key={post.id}>{post.title}</li>)
            )}
          </ul>
          {hasNextPage && <button onClick={() => fetchNextPage()}>Load More</button>}
        </>
      )}
    </div>
  );
}

Best Practices

  1. Use Query Keys Effectively: Query keys should uniquely identify the data being fetched. Use arrays for complex keys.

  2. Leverage Devtools: The React Query Devtools are invaluable for debugging and optimizing queries.

  3. Optimize Performance: Use staleTime and cacheTime to control how long data is considered fresh or cached.

  4. Handle Errors Gracefully: Always handle loading and error states to provide a smooth user experience.


Conclusion

TanStack Query is a game-changer for data fetching in React applications. Its intuitive API, powerful features, and excellent developer experience make it a must-have tool for modern web development. Whether you’re building a small app or a large-scale application, TanStack Query simplifies data management and helps you deliver a better user experience.

Start integrating TanStack Query into your React projects today, and unlock the full potential of efficient and scalable data fetching!