Mastering React Query: Simplify Your React Data Flow

Relia Software

Relia Software

Huy Nguyen

Relia Software

featured

React Query is a data synchronization library for React that simplifies fetching, caching, and state management of server state in React applications.

Mastering React Query: Simplify Your React Data Flow

Table of Contents

Frustrated with data fetching and state management in React? React Query, an open-source project created by Tanner Linsey, is your secret weapon. It simplifies fetching, caching, synchronizing, and updating server state, making your React development a breeze. This guide equips you to master React Query and take your skills to the next level!

>> Read more about React coding:

Requirements

To follow along with this guide, you’ll need to meet certain prerequisites:

  • Understanding of JavaScript syntax.
  • Basic familiarity with the React.js framework.
  • Knowledge of APIs.
  • Installation of React Query and Axios on your computer.

Throughout this guide, we’ll be using Jsonplaceholder as our API endpoint and React Query and Axios to fetch and manage server state data. The following is a demonstration of how to install and set up React Query in a React project. To get started, navigate to a React project and execute the following commands in your terminal:

npm install react-query axios

Navigate to your index.js file and paste the code below:

import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";

import "./styles.css";
import Application from "./Application";

const dataClient = new QueryClient();

ReactDOM.render(
  <QueryClientProvider client={dataClient}>
    <Application />
  </QueryClientProvider>,
  document.getElementById("root"),
);

Understanding React Query

React Query is a data synchronization library for React that simplifies fetching, caching, and state management of server state in React applications.

You might be curious about the connection between React Query and a real-world application. Let’s consider a popular e-commerce platform like Amazon. The core functionality of React Query mirrors Amazon’s product search in many ways - it fetches and caches data from an API, manages state, and optimizes data retrieval to enhance user experiences.

When we use Amazon to search for a product, it pulls the most relevant data from its vast database. Similarly, React Query retrieves data from an API endpoint, much like how Amazon pulls data from its database.

React Query also manages the state of the fetched data, akin to how Amazon handles the information it retrieves. React Query caches API responses, storing the fetched data locally to minimize the need for future fetches.

Just as Amazon caches product pages to decrease latency when displaying search results, React Query’s caching mechanism enhances performance and reduces the time taken to display the requested information. This comparison helps us understand the workings of React Query in a more relatable context.

Data Querying

This involves using a query language to request specific information from a database or dataset. Queries allow us to extract, filter, and manipulate data based on certain conditions and criteria.

Submitting a query lets you search for data that fulfills specific requirements. This is why “Query” is part of “React Query” - this library simplifies the process of handling and making queries. Now that we understand what data querying entails, let’s explore how to fetch basic data with React Query.

Benefits of Using React Query

React Query outperforms traditional state management tools like useEffect and others due to its built-in query caching. Once data is fetched, it’s stored in a cache and can be reused later, eliminating the need for redundant API calls.

React Query also automatically manages the state of queries, reducing the need for developers to write and maintain complex state management logic. It comes with built-in error-handling capabilities, enabling developers to handle API errors effectively.

React Query has an impressive list of features:

  • Caching: It stores the results of asynchronous actions, such as fetching data from an API, so that the same action doesn’t need to be repeated unnecessarily.
  • Deduping multiple requests for the same data into a single request: This means that if multiple components are requesting the same data at the same time, React Query will only make a single request.
  • Updating “out of date” data in the background: This can happen on windows focus, reconnect, interval, and so on.
  • Performance optimizations like pagination and lazy loading data: This allows for efficient data fetching, especially when dealing with large datasets.
  • Memoizing query results: This means that React Query will remember the results of previous queries and can return the cached result immediately if the same query is made again.
  • Prefetching the data: This means that React Query can fetch data in advance before it’s needed, improving the perceived performance of your application.
  • Mutations, which make it easy to implement optimistic changes: This allows for a more responsive user interface by assuming that most actions will succeed and updating the UI optimistically before the server responds.

React Query is a powerful tool that can greatly simplify the process of fetching and managing server state in web applications. After understanding the benefits of using React Query, let’s delve deeper into how to perform a basic data fetch with this library.

>> Read more: Mastering React Hooks: Fundamentals for Beginners

Executing Basic Data Fetching

This section dives into the heart of React Query - the useQuery hook, and demonstrates how to use it for basic data fetching.

Understanding the useQuery Hook

React Query provides a mechanism for managing server states through the useQuery hook. This hook is utilized to retrieve data from your API. It yields an object encompassing the query’s status (loading, error, or success), the data procured from the query, and functions to re-fetch the data.

Code Example

To illustrate its functionality, we will retrieve a list of post titles from the Jsonplaceholder API. Below is a rudimentary example of how you could employ React Query to fetch and handle server data:

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

// Function to fetch blog entries
const fetchBlogEntries = async () => {
  const result = await axios.get("https://jsonplaceholder.typicode.com/posts");
  return result.data;
};

// Component to display blog entries
const BlogEntries = () => {
  const { data: entries, error, isFetching } = useQuery("entriesData", fetchBlogEntries);

  if (isFetching) return <div>Loading blog entries...</div>;
  if (error) return <div>Error occurred: {error.message}</div>;

  return (
    <ul>
      {entries.map((entry) => (
        <li key={entry.id}>{entry.title}</li>
      ))}
    </ul>
  );
};

export default BlogEntries;

In this scenario, the useQuery hook is responsible for retrieving blog entries. It fetches data, manages loading states, and handles any errors that may occur.

The function fetchBlogEntries uses Axios to fetch the data. If the data is being fetched or if an error arises, a message is shown. If not, the blog entries are displayed as a list.

It’s also worth noting that the useQuery hook takes care of caching. When you invoke useQuery with a key (in this case, ‘entriesData’) and a fetch function (fetchBlogEntries), React Query executes the fetch and stores the result in a cache.

The provided key (‘entriesData’) serves as the identifier for this cache. If useQuery is invoked again with the same key and the data is still in the cache, React Query will return the cached data instead of executing a new fetch.

Upon running this in your browser, you will see a list of blog entry titles.

Data Mutations with React Query

This section dives into another core functionality of React Query - handling data mutations.

Understanding Data Mutation

Data mutation refers to the process of altering something in the database, which could involve posting, creating, or deleting something. Regardless of the action, it’s still considered mutation. This is where React Query shines as it can be utilized for CRUD (Create, Read, Update, and Delete) operations.

The useMutation Hook

React Query provides the useMutation hook to manage data mutations. It simplifies the process of sending mutation requests, handling responses, and updating your application state accordingly.

In other words, while the useQuery hook is used for “read” operations (data fetching), React Query offers the useMutation hook for “write” operations (creating, updating, and deleting data).

When using the useMutation hook for CRUD operations, it’s important to note that the Json placeholder API doesn’t actually save the created, updated, or deleted data. However, to confirm the operation was executed, it will return either a success statement or an error statement.

Code Example

Create: To make a post using useMutation, create a component and name it PublishArticle(). You can then paste the following code:

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

const PublishArticle = () => {
  const [header, setHeader] = useState("");
  const [content, setContent] = useState("");

  const mutationRequest = useMutation((newArticle) => axios.post("https://jsonplaceholder.typicode.com/posts", newArticle));

  const sendData = () => {
    mutationRequest.mutate({ header, content });
  };

  if (mutationRequest.isLoading) {
    return <span>Submitting...</span>;
  }

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

  if (mutationRequest.isSuccess) {
    return <span>Article submitted!</span>;
  }

  return (
    <div>
      <input type="text" value={header} onChange={(e) => setHeader(e.target.value)} placeholder="Header" />
      <input type="text" value={content} onChange={(e) => setContent(e.target.value)} placeholder="Content" />
      <button onClick={sendData}>Submit</button>
    </div>
  );
};
export default PublishArticle;

In the code above, useMutation is utilized to send updated data to the Json Placeholder API. The function given to useMutation acts as the mutation function. The mutation is carried out when mutation.mutate is called with the new post data.

Extension: We can broaden this functionality to edit posts. To achieve this, create a component named EditPost() , and include the subsequent code:

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

const EditPost = () => {
  const [postTitle, setPostTitle] = useState("");
  const [postBody, setPostBody] = useState("");

  // The mutation function is defined here using useMutation
  const postMutation = useMutation((newPost) => axios.put("https://jsonplaceholder.typicode.com/posts/1", newPost));

  // This function is used to submit the new post data
  const sendData = () => {
    postMutation.mutate({ title: postTitle, body: postBody });
  };

  // Different states of the mutation are handled here
  if (postMutation.isLoading) {
    return <span>Updating...</span>;
  }

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

  if (postMutation.isSuccess) {
    return <span>Post updated!</span>;
  }

  // The form for updating the post is rendered here
  return (
    <div>
      <input type="text" value={postTitle} onChange={(e) => setPostTitle(e.target.value)} placeholder="Title" />
      <input type="text" value={postBody} onChange={(e) => setPostBody(e.target.value)} placeholder="Body" />
      <button onClick={sendData}>Update</button>
    </div>
  );
};

export default EditPost;

In this code, we’ve modified the axios.post call within the useMutation hook to axios.put, enabling us to make a PUT request instead of a POST request.

We’ve also adjusted the URL to incorporate the ID of the post we want to update https://jsonplaceholder.typicode.com/posts/1. The remainder of the code remains unchanged, and with these modifications, we’ve successfully updated a post.

Removal: To remove a previously created post, a component called PostDeletion() should be created.

import React from "react";
import { useMutation } from "react-query";
import axios from "axios";

const PostDeletion = () => {
  const deletePostMutation = useMutation(() => axios.delete("https://jsonplaceholder.typicode.com/posts/1"));

  const handleDelete = () => {
    deletePostMutation.mutate();
  };

  if (deletePostMutation.isLoading) {
    return <span>Deleting post...</span>;
  }

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

  if (deletePostMutation.isSuccess) {
    return <span>Post has been deleted!</span>;
  }

  return (
    <div>
      <button onClick={handleDelete}>Delete Post</button>
    </div>
  );
};

export default PostDeletion;

To delete a post, we modified the axios.post call in the useMutation hook to axios.delete, thereby making a DELETE request instead of a POST request.

We also adjusted the URL to incorporate the ID of the post to be deleted https://jsonplaceholder.typicode.com/posts/1.

The steps outlined above demonstrate the optimal use of the useMutation hook, and it’s clear that it simplifies the task significantly.

Advanced Features of React Query

The useInfiniteQuery Hook: Effortless Pagination

React Query’s useInfiniteQuery hook is a powerful tool that allows you to fetch data in a paginated manner. This is particularly useful when dealing with large datasets that you don’t want to load all at once due to performance concerns.

Here’s a basic example of how you might use useInfiniteQuery:

import { useInfiniteQuery } from 'react-query';
import axios from 'axios';

function MyComponent() {
  const fetchPosts = ({ pageParam = 1 }) =>
    axios.get(`https://api.example.com/posts?page=${pageParam}`);

  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery('posts', fetchPosts, {
    getNextPageParam: (lastPage, pages) => lastPage.nextPage,
  });

  return (
    <div>
      {data.pages.map((group, i) => (
        <React.Fragment key={i}>
          {group.posts.map(post => (
            <p key={post.id}>{post.title}</p>
          ))}
        </React.Fragment>
      ))}
      <div>
        <button
          onClick={() => fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
        >
          {isFetchingNextPage
            ? 'Loading more...'
            : hasNextPage
            ? 'Load More'
            : 'Nothing more to load'}
        </button>
      </div>
    </div>
  );
}

useInfiniteQuery is used to fetch posts from an API. The fetchPosts function is passed as the fetcher function to useInfiniteQuery. The getNextPageParam option is used to determine the next page parameter from the last loaded page.

The fetchNextPage function is used to load the next page of data when the “Load More” button is clicked. The hasNextPage and isFetchingNextPage properties are used to manage the state of the “Load More” button. The posts are then rendered to the screen, grouped by page.

Please replace https://api.example.com/posts?page=${pageParam} with your actual API endpoint. This is just a placeholder for demonstration purposes. Also, ensure that the structure of the response from your API matches with the way data is accessed in this code. For instance, lastPage.nextPage and group.posts.map assumes specific structure of the API response. You might need to adjust this based on your API’s response structure.

Remember to handle errors and loading states as per your project requirements. This example focuses on the basic usage of useInfiniteQuery and does not include error or loading state handling.

This is a simple example and useInfiniteQuery has many more options and features you can use to customize the behavior to suit your needs.

Refine Framework: Extending Functionality

The Refine framework (React-based framework) offers enhanced versions of the hooks provided by React Query. Refine augments the functionality of React Query’s hooks, adds additional features and customization options to better accommodate data-intensive applications. Additional hooks provided are useUpdate and useList.

  • useUpdate: is an advanced version of the useMutation hook. This hook is utilized when there’s a need to update a record. It employs the update method as the mutation function from the dataProvider passed to refine. In short, useUpdate simplifies updating data within a collection, automatically handling optimistic updates and refetching related queries.
  • useList: is an advanced version of the useQuery hook. It’s used when there's a need to fetch data based on sort, filter, pagination, etc., from a resource. In short, useList manages a collection of data locally, providing methods for CRUD operations and keeping the local state in sync with the server.

If you’re looking for a framework that harnesses the power of React Query, Refine is an excellent choice as it addresses issues related to data querying and server state management complexity.

While exploring Refine can unlock further capabilities, it goes beyond the scope of this basic guide. For detailed information on Refine and its hooks, we recommend referring to the official Refine documentation: https://refine.dev/docs/guides-concepts/faq/

React Query VS. Redux/Apollo Client: A Brief Comparison

React Query and Redux/Apollo Client are popular libraries in the React ecosystem for managing state and data fetching. Here’s a brief comparison:

  • React Query is a data-fetching and state management library. It shines when dealing with server state which is asynchronous by nature. React Query provides out-of-the-box features like caching, synchronization, background updates, and much more.
  • Redux is a predictable state container designed to help you write JavaScript apps that behave consistently across client, server, and native environments.
  • Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL.

Here’s a simple code comparison of data fetching with React Query and Apollo Client:

  • React Query
// React Query
import { useQuery } from 'react-query';

const fetchPosts = async () => {
  const res = await fetch('/api/posts');
  return res.json();
};

function Posts() {
  const { data, isLoading, error } = useQuery('posts', fetchPosts);

  if (isLoading) return 'Loading...';
  if (error) return 'An error has occurred: ' + error.message;

  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
  • Apollo Client
// Apollo Client
import { useQuery, gql } from '@apollo/client';

const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
    }
  }
`;

function Posts() {
  const { loading, error, data } = useQuery(GET_POSTS);

  if (loading) return 'Loading...';
  if (error) return `Error! ${error.message}`;

  return (
    <ul>
      {data.posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

In both examples, we fetch a list of posts and display them. The main difference lies in how queries are made and results are read. React Query uses RESTful API (HTTP requests), while Apollo Client uses GraphQL queries.

Redux is not included in the code comparison because it’s not directly comparable to React Query or Apollo Client. Redux is a general-purpose state management library, while React Query and Apollo Client are more specialized for data fetching. However, Redux can be used in combination with other libraries like redux-thunk or redux-saga to handle data fetching and asynchronous actions.

Conclusion

In this introductory guide, we delved into the realm of React Query and its fundamental concepts. We discovered that React Query is a potent tool for managing data fetching, caching, and state in React applications.

Frameworks like refine amplify the capabilities of React Query, providing a comprehensive solution for data-intensive applications. By utilizing React Query, developers can boost the efficiency and user experience of their React projects. Thank you for reading!

>>> Follow and Contact Relia Software for more information!

  • coding
  • development