Code Premix

How to Display Loading, Error, and Success States with React Query

📅December 2, 2024
🗁React

Handling different states like loading, error, and success is a common task when working with asynchronous data in React applications. React Query makes this easier and more efficient with its powerful state management features. By using Type Narrowing, we can simplify and streamline how we display these states.

In this post, we’ll explore how to handle these states with React Query using TypeScript. We'll walk through an example to show how you can leverage type narrowing for clear, type-safe logic.

Step 1: Setting Up React Query and TypeScript

First, make sure you have React Query installed in your project. Here’s a basic setup:

npm install @tanstack/react-query

Wrap your app with the QueryClientProvider and configure the QueryClient:

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

const queryClient = new QueryClient();

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

Step 2: Using React Query in a Component

We’ll fetch user data from a sample API and display loading, error, and success states:

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

type User = {
  id: number;
  name: string;
};

export function MyComponent() {
  const { data, isLoading, isError } = useQuery<User, Error>(["user"], fetchUser);

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

  if (isError) {
    return <p>Error fetching data!</p>;
  }

  return (
    <div>
      <h1>User Data</h1>
      <p>ID: {data?.id}</p>
      <p>Name: {data?.name}</p>
    </div>
  );
}

async function fetchUser(): Promise<User> {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  if (!response.ok) throw new Error("Network error");
  return response.json();
}

Step 3: Leveraging Type Narrowing for Better Clarity

TypeScript automatically narrows types when checking isLoading or isError. This means you can write type-safe conditions with ease. Here’s how it works:

  • isLoading Narrowing: Ensures data and isError are irrelevant when loading.
  • isError Narrowing: Makes data accessible only in success states.

Here’s an updated version using TypeScript effectively:

if (isLoading) {
  return <p>Loading...</p>; // TypeScript knows data isn't available here
}

if (isError) {
  return <p>Error: {error.message}</p>; // TypeScript ensures error exists
}

return (
  <div>
    <h1>User Details</h1>
    <p>ID: {data?.id}</p>
    <p>Name: {data?.name}</p>
  </div>
);

Conclusion

React Query’s built-in state flags and TypeScript narrowing simplify handling various query states. By understanding how to use isLoading, isError, and data effectively, you can write cleaner and safer code. This approach not only improves readability but also ensures type safety throughout your app.