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:
- Mastering React Higher-Order Components (HOCs)
- Top 6 Best React Component Libraries for Your Projects
- The Best React Design Patterns with Code Examples
- React vs Angular: A Comprehensive Side-by-Side Comparision
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.
>> You may consider: React Suspense for Mastering Asynchronous Data Fetching
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 theuseMutation
hook. This hook is utilized when there’s a need to update a record. It employs theupdate
method as the mutation function from thedataProvider
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 theuseQuery
hook. It’s used when there's a need to fetch data based on sort, filter, pagination, etc., from aresource
. 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