Cleaner data fetching with react-query
Tags: react, javascript, api
Published: 2021-08-14 20:04:41

Data-fetching is something I feel does not get the importance and limelight as say state management in the React world. People often combine client state and server state into their state management solution. By server state, I mean the data from your backend servers. For example, in a redux setup, client state and data from server are stored in stores and updates are handled through actions and reducers. Client state change *usually* causes a change in server state, so combining them makes sense in most cases. But I feel they are two separate entities and if handled properly, we can even get rid of client side state management solutions in some cases.

I started looking at solutions to separate client and server state management. Coming into the React world from an Angular background, I wanted something simple as storing your server data in a service and inject it into your components, and you're good to go. In React, you'd have to maintain a global state management solution if the data you're fetching is needed in multiple components.

###react-query to the rescue

I then looked at libraries which performs data fetching and maintain server state. I stumbled up [react-query](https://github.com/tannerlinsley/react-query) and boom! It had everything I needed and more. It provided a way of maintaining a global context of server state, and also provided an excellent caching solution with minimal configuration. There's also [swr](https://github.com/vercel/swr) which is equally awesome.

###Example

Alright, enough chit-chat. Let's get to the code. Here's how I usually setup my React projects. I have a folder called `pages` which has all the top level routes. `components` folder houses all the UI components and a folder called `api` which has all the server side APIs.

Let's say we have a product entity. A product has all of CRUD operations attached to it. So, the following API calls need to be there in products
```
1. Fetch all products
2. Fetch a specific product
3. Add a new product
4. Edit a product
5. Delete a product
```

`react-query` provides us with a `useQuery` hook which we can use for all our queries. This should cover points 1 and 2 in the above list.
We'll create our own data-fetching hooks for product by wrapping `useQuery`.

Our product file in `api/product.js` looks something like this
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nhyr3c95kmwoyoswcpk.png)
 

Let's go over how we setup functions for data-fetching with `react-query`.

####Fetch products

Let's start with fetching products. The barebones implementation with `useQuery` would look like
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdgpz0tc7iaeda3bgfwn.png)
 Not much going on here. We pass a unique id `key` as the first argument to `useQuery` and a fetch function to actually make the API call.

If we want to use this in a component, we can do so like
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ramt3omdp5g38bnfbpz.png)

> It gives us loading and error states which I think is neat
 
We have a working data-fetching setup, but the feature set doesn't end here. Any listing page would have additional features like `search`, `filters`, `pagination` etc. This is where react-query makes it really simple to add these. Let's set these things up.

###Pagination

In our `Products` component, we can have `page` and `limit` values as state. `page` denotes the current page number and `limit` denotes the number of products to be displayed on the page.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xcl89utwht8nxwxjj2w7.png)
 

The next step would be to hook this up with our `useFetchProducts` hook. Lets make our `useFetchProducts` hook take in `page` and `limit` as arguments.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xql7s88mrwqlnfr9t0os.png)

Let's unpack what's going on here. The `useFetchProducts` hook now takes in `page` and `limit` as arguments. It also adds these two to the `key` and adds them to the fetch URL. 

Great! That's it. We now have our pagination implemented. Now, whenever the value of `page` and `limit` changes in our `Products` component, react-query would automatically fire the API request and update the UI.

###Search

Another important common feature is search. So, lets add a search on `name` field in products. As you might have guessed already, it is the exact same process as pagination. We'll have a `name` field in state, and this state value would be passed to our `useFetchProducts` hook.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bg6m84tcg3ui68664ntb.png)
 

Our `useFetchProducts` will look something like this.
 
 ![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyo6l17agcz6gpedon4b.png)
 
Similarly, we can hook any number of filtering/search parameters to out `useFetchProducts` hook. This hook can be used across multiple components without any global state management system.

##Caching

Caching is hands-down my favourite feature of react-query. With minimal code, we can setup a powerful caching system. In the case of our products example, let's say we want our products to be cached for 10 seconds. We can do this by adding the `staleTime` option.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9bdpqobky2euglhbu7u.png)
This would use the data from the cache whenever this hook is called with the same `page`, `limit` and `name`. 

> An important thing to understand here is the `key` option. The `key` uniquely identifies a request. So, a request with `page` as 1 is not the same as a request with `page` as 2. The value from the cache will only be used all three key values are same.

### Updating internal cache

`react-query` also give us access to its internal cache. We can update this cache whenever we want. What this means is, we can set internal cache values of individual products.

Imagine this, we have fetched a list of products and displayed them on screen. The user clicks on a product, we take them to the product screen. In the product screen, we would have to fetch the product's details and display it. But! We already have the product's details in react-query's internal cache. What if we can use that instead of making an API call?

Let's start with creating a `useFetchProduct` hook for fetching individual product.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0x5zscd3bce76gkac585.png)
 
Nothing crazy going on here. Pretty much the same thing we did before, just that it take `id` as an argument. 
The important thing to note here is `['product', id]` as the key. We are associating a product's `id` as its key.

Now to the interesting part, whenever we fetch the list of products, we set the internal cache with value of each individual product. `react-query` exposes a `useQueryClient` hook which gives us the internal cache.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tsd1xrpi318bujrysyur.png)

Whenever our list of products is successfully fetched, the `onSuccess` function is called which has the API response it. We loop through each product and store it in the cache using the `setQueryData` method.

Now, whenever the user moves to an individual product's page from the products list page, the value from the cache will be used rather than making an API call.

##Conclusion

So, I found react-query to be extremely simple and powerful data-fetching solution. After using react-query, I even removed global state management solutions from some of my projects. Go give them some love on their [repo](https://github.com/tannerlinsley/react-query)

Cheers!