Using React Query for Server State Management

Loading

React Query is a powerful library for managing server state in React applications. It simplifies data fetching, caching, synchronization, and state management of server-side data. React Query abstracts away the complexities of handling async operations, providing an intuitive API for working with remote data sources.

In this guide, we’ll dive into how React Query works, its key features, and how to use it to handle server state efficiently in your React applications.


1. What is Server State?

Server state refers to data that comes from an external server, API, or database. Unlike client state (local data maintained by the app), server state requires asynchronous fetching and updating via network requests.

Managing server state effectively involves:

  • Fetching data from a server.
  • Handling loading and error states.
  • Caching and synchronizing data.
  • Optimistically updating UI when making changes.

React Query simplifies the process of handling server state, ensuring that data is efficiently fetched, cached, and synchronized across components.


2. Why Use React Query?

While React’s built-in state management (via useState and useReducer) is great for local state, handling server state requires additional logic for fetching, caching, error handling, and background data synchronization. React Query provides a set of hooks that manage these concerns seamlessly.

Benefits of React Query:

  • Automatic Caching: Automatically caches server responses to prevent redundant network requests.
  • Data Synchronization: Keeps data in sync across components using background refetching.
  • Optimistic Updates: Supports optimistic updates for a smoother user experience.
  • Error Handling: Built-in support for loading, error, and success states.
  • Polling: Allows periodic refetching of data, ideal for real-time applications.
  • Pagination and Infinite Query: Built-in support for paginated and infinite scroll data.

3. Setting Up React Query

To start using React Query in your project, first, install the necessary package:

npm install react-query

After installation, you’ll need to wrap your app in the QueryClientProvider component to provide the React Query context.

Example Setup:

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

// Create a new instance of QueryClient
const queryClient = new QueryClient();

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

In this setup:

  • QueryClientProvider is the context provider for React Query.
  • QueryClient is the main object that manages cache and query behaviors.

4. Fetching Data with React Query

React Query provides the useQuery hook to fetch data from a server. The useQuery hook takes a unique query key (usually a string or an array) and a function to fetch data.

Example: Fetching Data with useQuery

import React from 'react';
import { useQuery } from 'react-query';

const fetchUser = async () => {
  const response = await fetch('https://api.example.com/user');
  if (!response.ok) {
    throw new Error('Error fetching user data');
  }
  return response.json();
};

const User = () => {
  const { data, error, isLoading, isError } = useQuery('user', fetchUser);

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

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
};

export default User;
  • useQuery automatically handles loading, error, and success states.
  • The query is identified by a unique key ('user' in this case), and React Query will cache and reuse the results for subsequent renders.

5. Mutating Data with React Query

In addition to fetching data, React Query also provides a useMutation hook for creating, updating, or deleting data on the server. Mutations allow you to change server state, and React Query optimizes the process with automatic cache updates and optimistic UI.

Example: Mutating Data with useMutation

import React, { useState } from 'react';
import { useMutation } from 'react-query';

const createUser = async (user) => {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    body: JSON.stringify(user),
    headers: {
      'Content-Type': 'application/json',
    },
  });
  if (!response.ok) {
    throw new Error('Error creating user');
  }
  return response.json();
};

const CreateUser = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const { mutate, isLoading, isError, error } = useMutation(createUser, {
    onSuccess: (data) => {
      // Handle successful mutation (e.g., show a success message)
      console.log('User created', data);
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    mutate({ name, email });
  };

  if (isLoading) return <p>Creating user...</p>;
  if (isError) return <p>Error: {error.message}</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <input
        type="email"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <button type="submit">Create User</button>
    </form>
  );
};

export default CreateUser;
  • useMutation is used to handle data modifications on the server.
  • You can handle successful mutations with the onSuccess callback, and it also provides automatic cache updates for optimized UI.

6. Caching and Background Refetching

React Query automatically caches the data returned from useQuery, so you don’t need to make redundant requests for the same data. You can configure how often React Query should refetch data, allowing for background synchronization.

Example: Configuring Cache and Refetching

const { data, isLoading } = useQuery('user', fetchUser, {
  staleTime: 60000, // Data is considered fresh for 1 minute
  refetchOnWindowFocus: false, // Prevent refetching on window focus
  refetchInterval: 30000, // Refetch data every 30 seconds
});
  • staleTime: Prevents refetching within the specified time period, enhancing performance.
  • refetchOnWindowFocus: Controls whether the query should automatically refetch when the window is focused.
  • refetchInterval: Automatically refetches data at specified intervals (useful for real-time apps).

7. Optimistic Updates

React Query allows you to perform optimistic updates, which show a change in the UI before the server responds. This is useful for actions like form submissions or deleting items, where users expect immediate feedback.

Example: Optimistic Updates with useMutation

const { mutate } = useMutation(deleteUser, {
  optimisticUpdate: (variables) => {
    // Optimistically update the UI
    // For example, immediately remove the user from the list
  },
  onSuccess: () => {
    // Perform any additional actions after the mutation succeeds
  },
});

Optimistic updates allow you to provide a smooth and immediate user experience.


8. Polling and Infinite Queries

React Query also supports polling for continuously updating data (e.g., live data feeds) and infinite queries for paginated data (e.g., an infinite scroll implementation).

Example: Infinite Query

const fetchPosts = async ({ pageParam = 1 }) => {
  const response = await fetch(`/api/posts?page=${pageParam}`);
  return response.json();
};

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
  'posts',
  fetchPosts,
  {
    getNextPageParam: (lastPage) => lastPage.nextPage, // Logic for determining the next page
  }
);

This allows you to manage paginated data in a simple and efficient manner, handling fetching the next page automatically.


Leave a Reply

Your email address will not be published. Required fields are marked *