Module: Data Fetching and Caching

React Query Basics

React Query Basics

React Query (now TanStack Query) is a powerful library for data fetching, caching, synchronization, and updating server state in React applications. It simplifies many common data fetching patterns and provides a great developer experience. This document covers the basics of getting started with React Query.

Why Use React Query?

Before diving into the code, let's understand why React Query is beneficial:

  • Simplified Data Fetching: Handles fetching, caching, background updates, and stale-while-revalidate strategies with minimal boilerplate.
  • Automatic Caching: Caches data automatically, reducing redundant API calls and improving performance.
  • Background Updates: Automatically refetches data in the background to keep it fresh.
  • Deduping: Avoids multiple identical requests.
  • Optimistic Updates: Allows you to update the UI immediately and then sync with the server in the background.
  • Pagination & Infinite Scrolling: Provides utilities for handling large datasets efficiently.
  • Devtools: Excellent devtools for inspecting and debugging queries.

Installation

Install React Query using npm or yarn:

npm install @tanstack/react-query
# or
yarn add @tanstack/react-query

Basic Usage: useQuery

The core of React Query is the useQuery hook. It takes a query key and a query function as arguments.

  • Query Key: A unique identifier for the query. This is typically an array of strings or numbers. React Query uses the query key for caching and re-fetching.
  • Query Function: An asynchronous function that fetches the data. This function should return a promise that resolves with the data.
import { useQuery } from '@tanstack/react-query';

async function fetchData(key) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${key[0]}`);
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
}

function MyComponent() {
  const { data, isLoading, isError, error } = useQuery(['todo', 1], fetchData);

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

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

  return (
    <div>
      <h1>Todo</h1>
      <p>Title: {data.title}</p>
      <p>Completed: {data.completed ? 'Yes' : 'No'}</p>
    </div>
  );
}

export default MyComponent;

Explanation:

  1. useQuery(['todo', 1], fetchData): This line initiates the query.

    • ['todo', 1] is the query key. We're fetching a specific todo item with ID 1. Using an array allows for more complex keys (e.g., ['todos', { page: 2, limit: 10 }]).
    • fetchData is the function that actually fetches the data.
  2. data, isLoading, isError, error: useQuery returns an object with several properties:

    • data: The fetched data. Initially undefined until the data is loaded.
    • isLoading: A boolean indicating whether the data is currently being fetched.
    • isError: A boolean indicating whether an error occurred during the fetch.
    • error: The error object if an error occurred.
  3. Conditional Rendering: The component renders different content based on the isLoading, isError, and data states.

Query Key Best Practices

  • Use Arrays: Arrays are preferred for query keys. They allow you to include multiple dependencies.
  • Serialization: Query keys should be serializable (e.g., strings, numbers, booleans, plain objects). Avoid using functions or complex objects directly.
  • Specificity: Make your query keys specific enough to differentiate between different data requests.

Query Options

useQuery accepts an optional third argument: an options object. This object allows you to customize the behavior of the query.

const { data, isLoading, isError, error } = useQuery(
  ['todos'],
  fetchTodos,
  {
    refetchOnWindowFocus: false, // Don't refetch when the window gains focus
    staleTime: 60 * 1000, // Data is considered stale after 1 minute
    cacheTime: 5 * 60 * 1000, // Data remains in the cache for 5 minutes
    retry: 3, // Retry the request up to 3 times
    onError: (error) => {
      console.error('Error fetching todos:', error);
    },
  }
);

Common Query Options:

  • refetchOnWindowFocus: Whether to refetch data when the window gains focus. Defaults to true.
  • refetchOnMount: Whether to refetch data when the component mounts. Defaults to true.
  • staleTime: How long data is considered "fresh" before React Query attempts to refetch it in the background. Defaults to 0 (always stale).
  • cacheTime: How long data remains in the cache after the last component unmounts. Defaults to 5 minutes.
  • retry: The number of times to retry the request if it fails. Defaults to 3.
  • onError: A callback function that is called when an error occurs.
  • onSuccess: A callback function that is called when the data is successfully fetched.
  • enabled: A boolean that determines whether the query should be enabled. Useful for conditional fetching.
  • select: A function that transforms the data before it's returned to the component.

The QueryClient

The QueryClient is a central object that manages the cache and provides methods for interacting with the query cache.

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

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <MyComponent />
    </QueryClientProvider>
  );
}

Explanation:

  1. new QueryClient(): Creates a new QueryClient instance.
  2. QueryClientProvider: Wraps your application with the QueryClientProvider component, making the QueryClient available to all components that use React Query.

Accessing the QueryClient

You can access the QueryClient instance using the useQueryClient hook:

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

function MyComponent() {
  const queryClient = useQueryClient();

  const invalidateQuery = () => {
    queryClient.invalidateQueries(['todo', 1]); // Mark the query as stale
  };

  return (
    <div>
      <button onClick={invalidateQuery}>Invalidate Query</button>
    </div>
  );
}

Common QueryClient Methods:

  • getQueryData(queryKey): Retrieves the cached data for a given query key.
  • setQueryData(queryKey, data): Updates the cached data for a given query key.
  • invalidateQueries(queryKey): Marks a query (or queries) as stale, triggering a refetch on the next access.
  • refetchQueries(queryKey): Immediately refetches a query (or queries).
  • removeQueries(queryKey): Removes queries from the cache.

This provides a foundational understanding of React Query. Further exploration of features like mutations, pagination, and devtools will unlock even more powerful data management capabilities in your React applications.