DEV Community

Cover image for Cleaner data fetching with react-query
Siddharth Venkatesh
Siddharth Venkatesh

Posted on • Updated on

Cleaner data fetching with react-query

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 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 which is equally awesome.


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
Enter fullscreen mode Exit fullscreen mode

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

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
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

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.


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.


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.


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.


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.


Our useFetchProducts will look something like this.


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 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.
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.

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.


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.


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


Top comments (16)

hyggedev profile image
Chris Hansen

I just woke up. It's a little after 7 a.m. Just finished the drip for my coffee. So now i'm chillen' and sippin', having a good ol' time. So I'm cruising through my feed, and I stumbled across this 5 minute read.

EXCELLENT. I have always wanted to dig a tad deeper into React-Query, but always came across resources that were a little verbose. This is so easy to read through, and to understand. Your code snippets are also so intuitive and very simple and align exactly with your guide. Thanks man, you gave me a great start to my morning!

I have a question. I don't like to store keys or anything sensitive in react apps, also why I moved over to Next.js... is React-Query safe to use on the client side? Or how would you recommend storing keys safely while using create-react-app. I am aware of reacts built-in REACT_APP_="key", but can still be exposed in the browser.

nizans profile image
Nitzan Savion

Thank for this very helpful!

How would you handle mutations in the same way?

leodiegoo profile image
Leonardo Diego

Does anyone know what theme he's using? 😅

siddharthvenkatesh profile image
Siddharth Venkatesh

Hey, the VSCode theme is called Synthwave '84 and I use Polacode for the pictures of code snippets

mahikaushi65741 profile image
mahi kaushik

Thanks for sharing this helpful article about using React Query for cleaner data fetching! I recently started using this library and it has definitely made my code much cleaner and easier to manage. For anyone interested in learning more, I recommend checking out this React Query blog. Keep up the great work!

kein1945 profile image

It’s similar to redux + normalizr libraries. But in case with normalizr we have more more powerful control of data.
What do you think about this approach?

siddharthvenkatesh profile image
Siddharth Venkatesh

From what I've seen of normalizr, it helps you frame your backend response into something that is easier to render in your UI. react-query mainly focuses on how you fetch your data. Its also intelligent in terms of caching and refetching.
If you do want to use normalizr with react-query, you can apply the normalize function in the onSuccess handler of useQuery.

caedes profile image
Romain Laurent

You will undoubtedly write much less code with react-query.

peteregbujie profile image
Peter Egbujie

Excellent post. So how can I access the cached data for a single post. Did you use useFetchProduct(id) or queryClient.getQueryData?

siddharthvenkatesh profile image
Siddharth Venkatesh

You can use useFetchProduct(id) to get data for a single post. If the data is stale, react-query will fetch from your server. If the cache contains the fresh data, it will return it from the cache.
The "staleTime" property determines how long your data is valid. If you give its value as 10000, your data will be valid for 10 seconds. In this 10 seconds, whenever you call useFetchProduct(id), react-query will return the data from the cache.
Hope it clears things up.

brandiqa profile image
Michael Wanyoike

Wish I read your article before writing mine on Firebase + React Query. Very clean implementation. Kudos!

siddharthvenkatesh profile image
Siddharth Venkatesh

Thank you so much. Glad you liked it.

davboy profile image
Daithi O’Baoill

Excellent post, thank you

lucasfrutig0 profile image

The onError native callback of react query, to work ok, needs of a httpCode specific?

ademola_isr profile image

I will be glad if the mutation can be explained

frankpoundz profile image
Frank Opare