What is React Query: everything you need to know

12 min read
May 25, 2023

This blog will dive into React Query, a fantastic library designed for React applications. 

It’s well-known for its versatility, user-friendliness, and efficiency, making it great for data fetching and state management. 

We’ll explore the main features and advantages of React Query, showing you how it can improve your web app development

So come along as we discover the capabilities of React Query and tap into its full potential to boost your coding experience.

What is React Query

React Query is a data-fetching and state management library for React applications that simplifies fetching, caching, and updating data.

React Query logo

source: Theodo

React Query provides:

  • An intuitive API for fetching data from RESTful or GraphQL APIs.
  • Caching the results.
  • Automatically updating the data when needed.

It also supports features such as pagination, retries, and real-time updates.

Some of the main benefits of using React Query include:

  • Improved performance due to intelligent caching and background fetching
  • Simplified data-fetching logic
  • Enhanced error handling and retry capabilities
  • Seamless integration with the React ecosystem

Let’s explore how React Query can fetch data within a React application.

Querying data with React Query

Fetching data with React Query is straightforward, thanks to its simple API and hooks-based approach.

React Query provides the useQuery hook for fetching data, which accepts a unique query key and an asynchronous function that brings the data. The hook returns an object containing the status, data, and error, among other properties.

Here’s an example of fetching data from a RESTful API using React Query and TypeScript:

import { useQuery } from "react-query";
import axios from "axios";

interface Post {
 id: number;
 title: string;
 body: string;
}

const fetchPosts = async (): Promise<Post[]> => {
 const response = await axios.get("https://jsonplaceholder.typicode.com/posts");
 return response.data;
};

const PostsList: React.FC = () => {
 const { isLoading, isError, error, data } = useQuery<Post[], Error>("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>
 );
};

export default PostsList;

Now that we’ve seen how to fetch data let’s explore how to update data using mutations in React Query.

Mutations and Updating Data

React Query makes updating data simple with the useMutation hook, which allows for easy handling of server mutations and local data updates.

The useMutation hook accepts an asynchronous function that performs the mutation, returning an object containing the mutation status, error, and a function to trigger the mutation.

Here’s an example of updating data using React Query and TypeScript:

import { useMutation, useQueryClient } from "react-query";
import axios from "axios";

interface Post {
id: number;
title: string;
body: string;
}
const updatePost = async (post: Post): Promise<Post> => {
const response = await axios.put("https://jsonplaceholder.typicode.com/posts/${post.id}", post);
return response.data;
};
const PostItem: React.FC<{ post: Post }> = ({ post }) => {
const queryClient = useQueryClient();
const mutation = useMutation(updatePost, {
onSuccess: (updatedPost) => {
queryClient.setQueryData(["post", updatedPost.id], updatedPost);
},
});
const handleUpdate = () => {
mutation.mutate({ ...post, title: "Updated Title" });
};
return (
<div>
<h3>{post.title}</h3>
<button onClick={handleUpdate} disabled={mutation.isLoading}>
Update Title
</button>
{mutation.isError && <p>Error: {mutation.error.message}</p>}
</div>
);
};
export default PostItem;

We use the `useMutation` hook to update the post data in this example. When the mutation is successful, we update the local cache using `queryClient.setQueryData`.

Now that we’ve covered fetching and updating data let’s explore how to use React Query with TypeScript for better type safety.

React Query with TypeScript

React Query and TypeScript can enhance the application’s type safety, maintainability, and scalability when used together.

TypeScript adds a layer of type-checking on top of JavaScript, ensuring your code is more maintainable and less error-prone. 

By adding type annotations to your React Query code, you can catch errors earlier in the development process and improve the readability of your code. 

As a result, TypeScript can reduce the time spent on debugging and improve the code’s quality.

React Query relies heavily on Generics to ensure type safety when fetching and caching data in React applications. 

React Query has expanded its use of Generics to support new features. For example, the useQuery hook now has four Generics:

export function useQuery<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
>

Here’s a breakdown of each Generic:

  • TQueryFnData: This is the type of data returned from the queryFn function.
  • TError: This is the type of error that is expected to be returned from the queryFn.
  • TData: This is the type of data that the data property will eventually have. It’s relevant only if the select option is used, which allows the data property to differ from the data returned by the queryFn. If not, the data property will default to the data returned by the queryFn.
  • TQueryKey: This is the type of the queryKey, which is only relevant if the queryKey is passed to the queryFn.

These Generics have default values, meaning that TypeScript will fall back to these default types if they are not explicitly specified.

Here’s an example of using Generics in React Query with TypeScript:

interface User {
  id: number;
  name: string;
  email: string;
}

interface Post {
  id: number;
  title: string;
  body: string;
  authorId: number;
}

const fetchUser = async (id: number): Promise<User> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  return response.json();
};

const fetchUserPosts = async (userId: number): Promise<Post[]> => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
  return response.json();
};

const UserPosts = ({ userId }: { userId: number }) => {
  const { data: user, isLoading: isUserLoading, isError: isUserError, error: userError } = useQuery<User, Error>(
    ["user", userId],
    () => fetchUser(userId)
  );

  const { data: posts, isLoading: isPostsLoading, isError: isPostsError, error: postsError } = useQuery<
    Post[],
    Error,
    Post[],
    [string, number]
  >(
    ["userPosts", userId],
    () => fetchUserPosts(userId),
    {
      select: (data) => data.filter((post) => post.authorId === userId),
    }
  );

  if (isUserLoading || isPostsLoading) return <div>Loading...</div>;

  if (isUserError || isPostsError) return <div>Error: {userError?.message ?? postsError?.message}</div>;

  return (
    <div>
      <h1>{user.name}'s Posts</h1>
      <ul>
        {posts?.map((post) => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

In this example, the useQuery hook is used to fetch and cache data from an API. The first Generic, TQueryFnData, is explicitly set to Post[], the expected return type of the fetchPosts function.

The second Generic, TError, is set to Error, the predicted error type that may return from the fetchUserPosts function.

The data property returned from the useQuery hook is of type Post[] | undefined, which is inferred by TypeScript based on the first Generic provided to useQuery. 

This type of safety ensures that the posts array will contain only valid Post objects and that any potential errors will be of the expected Error type.

The TData and TQueryKey Generics handle more complex use cases in this example. The TData Generic is used to specify the expected return type of the fetchUserPosts function, which is Post[]. 

The TQueryKey Generic determines the desired shape of the queryKey, an array with a string and a number representing the user ID. This is useful in cases where the query depends on multiple variables or parameters.

Additionally, the select option is used in the useQuery hook for posts, which allows the data property to differ from what the queryFn returns.

In this case, the data property is filtered only to include posts by the specified user ID. This is possible by setting the TData Generic to Post[] and using the select option to manipulate the returned data.

Using the TData and TQueryKey Generics allows for greater type safety and flexibility in handling complex data structures and query dependencies.

Performance and optimizations

React Query offers numerous performance optimizations, such as intelligent caching, background fetching, and automatic retries, making it an ideal choice for high-performance applications.

Using React Query’s performance optimizations, developers can build smooth and efficient applications with minimal manual configuration.

Some of the performance optimizations provided by React Query include:

  • Intelligent Caching – automatically caches fetched data, reducing the number of requests to the server and improving the overall performance of your application.
  • Background Fetching – performs background fetching to keep data up-to-date, minimizing the impact on user experience and ensuring fresh data is always available.
  • Automatic Retries – automatically retries failed requests, providing a more robust and resilient data-fetching experience.
  • Query Invalidation and Refetching – allows developers to easily invalidate and refetch queries when data changes, ensuring the application always displays the latest information.

Here’s an example of how React Query handles caching and background fetching with TypeScript:

import { useQuery } from "react-query";
import axios from "axios";

interface Post {
  id: number;
  title: string;
  body: string;
}

const fetchPosts = async (): Promise<Post[]> => {
  const response = await axios.get("https://jsonplaceholder.typicode.com/posts");
  return response.data;
};

const PostsList: React.FC = () => {
  const { isLoading, isError, error, data } = useQuery<Post[], Error>("posts", fetchPosts, {
    cacheTime: 5 * 60 * 1000, // Cache data for 5 minutes
    staleTime: 1 * 60 * 1000, // Data is considered stale after 1 minute
    refetchOnWindowFocus: true, // Refetch data when the window regains focus
  });

  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>
  );
};

export default PostsList;

In the next chapter, we’ll explore how React Query stacks up against its competitors and why it may be the best choice for your next project.

React Query vs Competitors

Comparing React Query with its competitors, we’ll emphasize its distinct advantages and discuss why it has emerged as a top choice for modern web app development.

The primary competitors of React Query include Apollo Client and Redux, both of which have been widely used in the past.

Redux logo

source: typeofnan

However, React Query has risen to prominence due to its ease of use, performance, and versatility.

Now, let’s dive deeper into the features that set React Query apart from its competitors. 

React Query simplifies data-fetching and state management by providing robust features and an intuitive API.

React Query’s API is designed to be easy to learn and work with, allowing developers to manage server state in their applications efficiently. This simplicity directly contrasts Redux, which often requires boilerplate code and a complex setup.

In addition to the ease of use, React Query also excels in performance optimization.

React Query optimizes performance through features like caching, background fetching, and automatic garbage collection.

Caching allows React Query to store and reuse fetched data, which reduces the number of requests made to the server and improves the application’s performance. 

In addition, background fetching enables applications to refresh data without interrupting the user experience, while automatic garbage collection helps free up memory by removing unused data.

These performance benefits are crucial in modern web applications, where user experience and responsiveness are paramount.

Apollo client logo

source: Flavio Copes

React Query’s flexibility and extensibility make it a better developer choice than Apollo Client.

While Apollo Client is an excellent library for GraphQL data management, it’s limited in its scope and can be cumbersome to set up for smaller projects.

React Query, on the other hand, is easy to integrate and agnostic to the underlying API technology, making it a versatile choice for various types of applications.

In conclusion, React Query’s ease of use, performance optimization, and flexibility make it the preferred choice for modern web applications.

React Query has proven to be a valuable tool for developers, outshining its competitors in various aspects.

Additional Resources and Next Steps

To expand your understanding of React Query and stay updated on its latest capabilities, exploring further resources and planning your approach to becoming proficient in this comprehensive library is essential.

Learning is a continuous process, and to fully utilize React Query in your projects, it’s crucial to keep up with the latest updates, best practices, and community-driven content.

Here are some resources to help you continue your React Query journey:

Official React Query Documentation: The official documentation is a comprehensive guide covering all React Query aspects. It’s a great starting point for learning and reference.

React Query Essentials Course: This course, created by Tanner Linsley, the author of React Query, provides a deep dive into the library’s concepts and features.

React Query GitHub Repository: The GitHub repository contains the library’s source code, examples, and a wealth of information in the form of issues and pull requests.

React Query Community on GitHub Discussions: Engage with the community, ask questions, and share your experiences using React Query.

Armed with the knowledge you’ve gained from this blog post and the additional resources, you’re well-equipped to tackle the challenges of data fetching and state management in your React applications. 

Don’t hesitate to explore new features, experiment with different use cases, and contribute to the growing React Query community.

Conclusion

React Query is an essential library for modern React apps, simplifying fetching, caching, and updating data.

 Its seamless integration with TypeScript ensures type safety and enhances the overall development experience. 

By harnessing the power of React Query, developers can build high-performance applications that provide a smooth and responsive user experience.

Consider adopting React Query in your next project to unlock its full potential and elevate your application development process.

Also, if you’d like to read more about web apps and all things software, check out our blog.

Categories
Written by

Ivan Starcevic

frontend Developer

Related articles