React Query - The Definitive Guide to Making Requests in React
You might have been fetching data in your React Apps WRONG β
React itself does not have any opinions about how we fetch data from the Server.
The most commonly used are FetchAPI and the Axios library.
Wrong Approach 1 : Fetch API π£
This is the most basic approach to use the Browser's Fetch API.
It is called when a component first mounts, using the useEffect
hook and manage the response with useState
. But, this is not the recommended way for good developers(not even the best ones).
Fetch is not a good option because of :
- A lot more code is required for simple tasks.
- Lack of any useful functionality that makes the developer experience very bad.
Wrong Approach 2 : Axios πͺ
The Axios library simplifies the above code a bit, but still lacks some features for a great developer experience.
It abstracts the code for fetching, but does not help in :
- Caching.
- Data states like Error or loading.
- Still has try-catch hell.
Now that the above 2 magical ways are wrong, what is the correct way?
React Query - A Library that simplifies the way we fetch, cache and synchronize data from a server
Yes, both of the above are the same thing.
Let's see what React Query or Tanstack Query does and is in detail.
It is not Just A Data Fetching Library
What React Query gives you is not just the ability to describe which data you want and to fetch that data, it also comes with a cache π§ for that server data.
This means that you can just use the same useQuery
hook in multiple components, and it will only fetch data once and then subsequently return it from the cache
.
Enough with the theory, show me the Code β¨
Now, let's see how to set it up and some use cases where React Query just feels awesome.
Setup (The Boring Part)
First of all you need to have a project setup, either in ReactJS
or in NextJS
.
I will just use React Query in my Lynkitπ project.
To add React Query to the project, we use the following command :
npm i @tanstack/react-query
Now if you have used something like Redux
or ContextAPI
before, you have to make the client available to all the parts that you will be using.
The simplest way to do this is by wrapping the parts of your application inside a client-provider
, which in this case is called the QueryClientProvider
.
We also need to create a client which handles all the functionality of the Library.
[!Important]
Client is nothing but an instance of the base class of React Query in this case. It handles all the functionalities that we want to use.
For my project(an in most cases), I want to access the QueryClient
from all scopes of the App
, so we wrap the whole App component inside it.
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>
);
Now that React Query is Setup, it is time to look at it's various use cases.
Fetching Data
When making a Get Request, we use the useQuery
hook provided by the React Query library.
Note : If hooks are something you don't understand, I want you to stop here and look at this : Hooks : JS in plain english by Ayush Verma.
Okay, so useQuery hook.
Queries are basically asynchronous requests tied to a data source. And async requests are handle by promises in Javascript.
So, the useQuery
hook are used to subscribe to a query that listens to the pending
, fulfilled
and rejected
states and returns a state based on that.
A lot of jargon? Yeah, agreed. So here's a diagram :
Now the hooks take in 2 parameters mainly :
- A unique key π for the query
- A function that returns a promise that:
- Resolves the data β , or
- Throws an error β
π’ The unique key
you provide is used internally for refetching, caching, and sharing
your queries throughout your application.
Let's see an example from Lynkit where I fetch the logged in user data :
The Actual Data Fetching using Axios.get()
Yes, for fetching data we have to use Axios but not directly.
We define a function to get the data and add it as a Query Function to the User key.
So, whenever I want to use the User data, I can just use the UseQuery hook to get the user data :
- If found in the Query cache, it returns the data from there.
- Other it calls the following function :
export const getUserData = async() : Promise<ProfileData> => {
try {
const response : AxiosResponse = await axios.get("http://localhost:3003/user", {
headers : {
"Content-Type" : "application/json",
},
withCredentials: true,
});
const {status, data } = response;
console.log(data)
if(status!==200 || !data.user){
...
return nullUser;
}
return data.user as ProfileData;
} catch (error) {
console.log(error);
throw new Error("Data Fetching Failed")
}
}
The UseQuery hook is used as follows :
const {data, isError, isLoading} = useQuery<ProfileData>(
{
queryKey : ["user"],
queryFn : getUserData,
notifyOnChangeProps : ['data','error'],
refetchOnWindowFocus: false
}
)
The above works as follows :
Step 1 : Read the Query Key passed, in this case : ["user"]
Step 2 : Check if query key is present in the Query Cache
and return if present.
Step 3 : If the data is not present in the cache, called the function passed in the queryFn
property.
π Note that useQuery
hook is used only for get β©
requests. For other requests like, we use the useMutation
hook with the mutate method.
Invalidating Queries βπ
There will be cases, where you want to re-fetch data when an action occurs.
React Query also provides a way for us to do that : client.invalidateQueries()
method.
You need to pass in the same queryKey
that you want to be re-fetched and the QueryClient auto fetches the data when that data is invalidated.
const queryClient = useQueryClient();
queryClient.invalidateQueries(["user"]);
The cached user data is removed and the new data from API or server is fetched again at this point.
The above can be used in 2 conditions :
- The data is updated by adding, editing or deleting some part(like updating user name or password or image).
-
Logging out β
the user.
At this point you are ready to develop a sophisticated query based application.
For more detailed and complete explanations, you can check out @tkdodo π profile and the Complete Tanstack Docs π‘.
You can also checkout my previous blog on Bun - Fastest Javascript Runtime β‘