DEV Community

Pedro Henrique Machado
Pedro Henrique Machado

Posted on

TanStack React Query: Crash Course

React Query, now known as TanStack React Query, is a powerful data-fetching and state management library for React applications. It simplifies the process of fetching, caching, synchronizing, and updating server state, making your React apps more efficient and easier to manage. In this guide, we'll walk through everything you need to know about React Query, from setup to advanced features like mutations and optimistic updates.

Prefer watching? Check out my tutorial:

Table of Contents

  1. Introduction to React Query
  2. What is TanStack React Query?
  3. Setting Up Your Project
  4. Installing React Query Library
  5. Configuring Query Client Provider
  6. Fetching Data from an API
  7. Using the useQuery Hook
  8. Handling Loading and Error States
  9. Understanding Caching in React Query
  10. Fetching Data by ID Example
  11. Introduction to Mutations
  12. Creating a Post with React Query
  13. Handling Post Creation Success
  14. Implementing Optimistic Updates
  15. Conclusion and Next Steps

Introduction to React Query

React Query is a library that manages server state in React applications. Unlike client state, which is typically managed with tools like Redux or the Context API, server state involves asynchronous data fetching, caching, synchronization, and more. React Query abstracts these complexities, allowing developers to focus on building features rather than managing data flows.

Key Benefits:

  • Simplifies data fetching and caching
  • Provides built-in support for background updates and refetching
  • Handles complex scenarios like pagination and infinite scrolling
  • Improves performance by minimizing unnecessary re-renders

What is TanStack React Query?

TanStack React Query is the latest iteration of React Query, maintained by Tanner Linsley and the open-source community. It extends the capabilities of the original React Query library, offering enhanced features and better performance. The name change to TanStack reflects the broader scope of the library beyond just React, although React remains its primary focus.

Core Features:

  • Declarative Data Fetching: Use hooks like useQuery and useMutation to declaratively fetch and mutate data.
  • Caching: Intelligent caching mechanisms to reduce redundant network requests.
  • Background Updates: Automatically refetch data in the background to keep the UI fresh.
  • Devtools: Powerful developer tools for debugging and optimizing queries.

Setting Up Your Project

Before diving into React Query, ensure you have a React project set up. You can create a new project using Create React App or any other preferred setup.

npx create-react-app react-query-tutorial
cd react-query-tutorial
Enter fullscreen mode Exit fullscreen mode

Installing React Query Library

Install the React Query library along with its devtools for enhanced debugging.

npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
Enter fullscreen mode Exit fullscreen mode

Alternatively, using Yarn:

yarn add @tanstack/react-query
yarn add @tanstack/react-query-devtools
Enter fullscreen mode Exit fullscreen mode

Configuring Query Client Provider

React Query requires a QueryClient and a QueryClientProvider to be set up at the root of your application. This setup provides the necessary context for all React Query hooks to function.

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • QueryClient: Manages the cache and configurations.
  • QueryClientProvider: Provides the QueryClient to the React component tree.
  • ReactQueryDevtools: Optional devtools for inspecting queries.

Fetching Data from an API

Let's fetch data from a public API. We'll use the JSONPlaceholder API for demonstration.

Example: Fetching a List of Posts

src/App.js

import React from 'react';
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 App() {
  const { data, error, isLoading } = useQuery(['posts'], fetchPosts);

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

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {data.map(post => (
          <li key={post.id}>
            <strong>{post.title}</strong>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • useQuery Hook: Fetches data and manages loading and error states.
  • Query Key: ['posts'] uniquely identifies the query.
  • Fetch Function: fetchPosts fetches data from the API.

Using the useQuery Hook

The useQuery hook is central to React Query. It handles fetching, caching, and updating data.

Syntax:

const { data, error, isLoading, isError, refetch } = useQuery(queryKey, queryFn, options);
Enter fullscreen mode Exit fullscreen mode

Parameters:

  • queryKey: A unique key for the query, can be a string or an array.
  • queryFn: The function that fetches the data.
  • options: Configuration options for the query.

Example:

const { data, error, isLoading } = useQuery(['user', userId], () => fetchUser(userId));
Enter fullscreen mode Exit fullscreen mode

Handling Loading and Error States

React Query provides boolean flags to handle different states of a query.

Common Flags:

  • isLoading: Indicates if the query is in a loading state.
  • isError: Indicates if the query encountered an error.
  • error: Contains the error object if isError is true.
  • isSuccess: Indicates if the query was successful.

Example:

if (isLoading) {
  return <div>Loading...</div>;
}

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

return <div>Data loaded successfully!</div>;
Enter fullscreen mode Exit fullscreen mode

Understanding Caching in React Query

Caching is one of the standout features of React Query. It stores fetched data, reducing the need for repeated network requests.

Key Concepts:

  • Stale Time (staleTime): Duration for which the cached data is considered fresh.
  • Cache Time (cacheTime): Duration for which unused cached data remains in memory.
  • Query Invalidation: Mechanism to mark cached data as stale.

Example:

useQuery(['posts'], fetchPosts, {
  staleTime: 1000 * 60 * 5, // 5 minutes
  cacheTime: 1000 * 60 * 10, // 10 minutes
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Data is fresh for 5 minutes. Within this period, React Query will not refetch the data.
  • Cached data remains for 10 minutes after it's no longer used.

Fetching Data by ID Example

Fetching individual items by ID is a common pattern. React Query handles it efficiently by using dynamic query keys.

Example: Fetching a Single Post

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

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

function Post({ postId }) {
  const { data, error, isLoading } = useQuery(['post', postId], () => fetchPostById(postId));

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

  return (
    <div>
      <h2>{data.title}</h2>
      <p>{data.body}</p>
    </div>
  );
}

export default Post;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The query key ['post', postId] ensures each post has a unique cache entry.
  • Changing postId triggers a new fetch for the corresponding post.

Introduction to Mutations

While useQuery handles data fetching, mutations manage data updates like creating, updating, or deleting records. React Query provides the useMutation hook for this purpose.

Key Features:

  • Handles asynchronous operations.
  • Manages loading and error states for mutations.
  • Supports optimistic updates for better UX.

Creating a Post with React Query

Let's create a new post using the useMutation hook.

Example: Creating a New Post

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

const createPost = async (newPost) => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(newPost),
  });
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
};

function CreatePost() {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const queryClient = useQueryClient();

  const mutation = useMutation(createPost, {
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries(['posts']);
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ title, body });
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Create New Post</h2>
      <div>
        <label>Title:</label>
        <input value={title} onChange={e => setTitle(e.target.value)} required />
      </div>
      <div>
        <label>Body:</label>
        <textarea value={body} onChange={e => setBody(e.target.value)} required />
      </div>
      <button type="submit">Create</button>
      {mutation.isLoading && <p>Creating post...</p>}
      {mutation.isError && <p>Error: {mutation.error.message}</p>}
      {mutation.isSuccess && <p>Post created!</p>}
    </form>
  );
}

export default CreatePost;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • useMutation Hook: Handles the post creation process.
  • onSuccess Callback: Invalidates the ['posts'] query to refetch the updated list.
  • Form Handling: Captures user input and triggers the mutation on form submission.

Handling Post Creation Success

After successfully creating a post, it's essential to update the UI to reflect the new data. React Query facilitates this through query invalidation and refetching.

Steps:

  1. Invalidate Queries: Marks specific queries as stale.
  2. Refetch Data: Automatically refetches the invalidated queries to update the cache.

Example:

const mutation = useMutation(createPost, {
  onSuccess: () => {
    // Invalidate the 'posts' query to refetch the posts
    queryClient.invalidateQueries(['posts']);
  },
});
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Ensures the UI displays the latest data.
  • Maintains consistency between the client and server states.

Implementing Optimistic Updates

Optimistic updates enhance user experience by immediately reflecting changes in the UI before the server confirms them. React Query supports this feature, allowing for snappy and responsive interfaces.

Example: Optimistic Update on Post Creation

const mutation = useMutation(createPost, {
  onMutate: async (newPost) => {
    await queryClient.cancelQueries(['posts']);

    const previousPosts = queryClient.getQueryData(['posts']);

    queryClient.setQueryData(['posts'], old => [...old, { id: Date.now(), ...newPost }]);

    return { previousPosts };
  },
  onError: (err, newPost, context) => {
    queryClient.setQueryData(['posts'], context.previousPosts);
  },
  onSettled: () => {
    queryClient.invalidateQueries(['posts']);
  },
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • onMutate: Called before the mutation function. Cancels any ongoing queries and updates the cache optimistically.
  • onError: Reverts the cache to its previous state if the mutation fails.
  • onSettled: Invalidates the query to ensure data consistency.

Benefits:

  • Provides immediate feedback to users.
  • Enhances perceived performance.

Conclusion and Next Steps

React Query (TanStack React Query) is a robust library that simplifies data fetching and state management in React applications. By leveraging features like caching, background updates, and mutations, developers can build efficient and responsive interfaces with ease.

Next Steps:

  • Explore Pagination and Infinite Queries: Handle large datasets and infinite scrolling.
  • Advanced Caching Strategies: Customize caching behaviors to suit your application's needs.
  • Server-Side Rendering (SSR): Integrate React Query with frameworks like Next.js for SSR support.
  • Authentication and Authorization: Manage authenticated requests and secure data fetching.

By mastering React Query, you'll be well-equipped to handle complex data scenarios and build scalable React applications.


Resources:

Top comments (0)