In the previous lesson, we discussed the setup process for React query and how to make the query client instance available across our components. In this lesson, we will learn, how we can use React query for fetching and managing data from an API. To facilitate this process, React query provides a hook called useQuery
.
The useQuery
hook supports various configuration options, the two main options are:
queryKey
: It is a unique identifier used by React query to identify, cache, and re-fetch the query result. It can be any string or an array with a string and any number of serializable objects.
queryFn
: It is a function that performs the actual data fetching.
To illustrate the usage of useQuery
, let's consider an example where we want to fetch and manage data from the product GET API.
We start by setting the queryKey
option within the useQuery
hook. We pass an array that includes the string "products"
, which represents the key for the products query, and the slug mens-cotton-jacket
. This ensures that the query is specific to this product. The next time you fetch product details for mens-cotton-jacket
, the React query checks if the query has been previously cached. If it is cached, it returns the cached data instead of making a new network request.
Next, we will specify the queryFn
option, which calls the productsApi.show
method to fetch the product data for the slug mens-cotton-jacket
.
The useQuery
hooks return an object with several properties containing information about the query's state and data. Here are the main properties returned by the hook:
data
: It is the return value of queryFn
. In our case, it is the API response object.
isLoading
: A boolean flag that indicates whether the query is currently in the process of fetching data for the first time. It will be true
while the query is loading and false
once the data has been fetched or if there was an error.
isError
: It is a boolean value that returns true
if an error occurs during the data fetching process otherwise, it returns false
.
isFetching
: It is a boolean value that returns true
whenever a query is being fetched, regardless of whether it is the initial fetch or a refetch. Once the data has been fetched or if there was an error, it returns false
.
Instead of directly calling the useQuery
hook inside our component, we will encapsulate the useQuery
hook inside a custom hook signifying the name of the query that it handles. We will be placing all such hooks related to products API under a single file called useProductsApi.js
inside the src/hooks/reactQuery
folder to keep it organized. The convention we follow is formatting the file name as use*Api
, where *
represents the specific set of APIs we are using. We follow this convention at BigBinary to organize the hooks related to react query.
Use the below command to create the useProductsApi.js
file.
We will be using the query key products
in multiple places, so we should move it to a constant file. We should store the query keys in the src/constants/query.js
file. Run the below command to create query.js
file.
Inside the query.js
file, let's add the query key called products
with the key PRODUCTS
to uniquely identify the queries related to products.
Define a hook to fetch product data
Inside the useProductsApi.js
file, we will define the useShowProduct
hook, which takes a single parameter slug
. Within this hook, we will call the useQuery
hook to handle the fetching and management of data associated with a product.
We will now update the existing logic related to fetching the product details in the Product/index.jsx
by replacing the useEffect
, fetchProduct
, and useState
with the useShowProduct
hook, passing slug
as an argument.
To improve readability, we will alias the data
property returned by the useShowProduct
hook to product
. We will also set its default value to an empty object. By setting the default value, we can avoid potential issues that may arise if the data
value is not available or not yet loaded.
Define a hook to fetch the product list
Similarly, we will define a custom hook called useFetchProducts
inside the useProductsApi.js
file to fetch the product list.
Just like the API connector productsApi.fetch
, the useFetchProducts
hook takes a single parameter params
. We will forward params
to productsApi.fetch
via the queryFn
. Let us use QUERY_KEYS.PRODUCTS
and params
as the queryKey
.
Now let's replace the existing logic for fetching the product list in the ProductList/index.jsx
with the useFetchProducts
hook, passing debouncedSearchKey
to the searchTerm
param.
We will destructure the products
property from data
and default it to an empty array for our convenience. Since the API results may not be available immediately, we are setting data
to default to an empty object {}
.
React query reuses the API result even if it is called from multiple components using the same key. It won't trigger multiple API calls. Let us use this knowledge to refactor our Carousel
component.
In the Product/index.jsx
file, we currently pass imageUrls
and title
as props to the Carousel
component. We no longer need to prop-drill those values. We can directly retrieve product details from useShowProduct
hook within the Carousel
component.
First, let's go to the Product/index.jsx
file, and remove the props passed to the Carousel
component.
Then, go to the Product/Carousel.jsx
and call the useShowProduct
hook. This hook expects the slug
as an argument. To access the slug
from the URL parameter, we can use the useParams
hook from react-router-dom
.
Avoid prop drilling values from React query hooks
React query sends a single API request even if it is called multiple places. So its value need not be prop drilled to child components. We can call the hook inside the child component to get all the required values.
In our case, we are passing down the availableQuantity
prop through multiple levels just to utilize it within the ProductQuantity
component. The flow of availableQuantity
prop through various component is shown below:
Given that the product slug
is accessible in the ProductQuantity
component, we can make use of the useShowProduct
React query hook to get the availableQuantity
for that product:
Now, let's remove the availableQuantity
that is prop drilled through the ProductListItem
, AddToCart
, Product
, and ProductCard
components.
Let's commit the new changes:
You can verify the changes here.