Usequery simplifies API Requests with React Query
In this post you will learn what is React Query and why does it make a lot of sense to use this library inside your React project.
As you can see I'm in the official website of React Query which is called TanStack. Here we are talking about library of version 4. Inside TanStack we have several libraries like Table, Router, Charts and others.
So the first question is what is React Query.
React Query is a library for data fetching, caching and synchronising data with the component state.
Typically inside our application we have a component
const Users = () => {
const [inputValue, setInputValue] = useState("");
const [users, setUsers] = useState([])
const addUser = () => {
createUser().then(user => {
setUsers([...users, newUser])
})
setInputValue('')
}
useEffect(() => {
getUsers().then(response => {
setUsers(response.data)
})
}, []);
return (
<div>
<div>Total: {users.length}</div>
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={addUser}>Add user</button>
</div>
<div>
{users.map((user, index) => (
<div key={index}>{user.name}</div>
))}
</div>
</div>
);
};
Here we have a state for our users and when we fetch data on initialize we must fulfill our state. After this later we can create new user and update users state.
This is totally fine and this is how we write code in React out of the box. What is the problem?
We use useState + useEffect everywhere just to fetch data and set a state.
React Query implements for us exactly this logic.
const Foo = () => {
const {isLoading, error, data} = useQuery(['users'], getUsers)
}
This is an example how we can fetch data from API and get directly local properties by using useQuery
hook.
React Query eliminates the whole bunch of logic that we must write every single time to synchronize API call with local state.
If you are still not sure what is React Query you might think about it like for example Apollo Client or RTK Query if you are familiar with them.
How can I use React Query?
This is why let's look on the real example and refactor it to React Query.
As you can see this is a project that we want to refactor. We fetch a list of users, we can type a new user and click add, which will send an API request and create new user.
Which actually means we have 2 real requests that we want to refactor. This is exactly what you saw in the code above that I showed you.
Our first step is to install 2 additioanl packages.
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
We install a library itself and a tool which will help us to debug react-query later.
Now we must initialize React Query correctly.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
</React.StrictMode>
);
As you can see here we must wrap our App
with QueryClientProvider
. This is a component of React Query which will allow us to make requests. Additionally we must create queryClient
and pass it inside.
Also we imported ReactQueryDevtools
component and rendered it after our App
. It is not mandatory but will help us tremendously in debugging.
useQuery
Now we can jump back and change our Users
component.
import { useQuery } from "@tanstack/react-query";
const Users = () => {
const { data: users = [] } = useQuery(["users"], getUsers);
}
This is a single liner that we need in order to fetch users from API. Inside useQuery
we must provide a path that we want to cache. In our case it's [users]
and a promise to get our data. We also removed useEffect
from the component as well as useState
for users because we don't need them anymore.
As you can see we get back a data
property. As we have users
inside our component we rename it and default to an empty array. Additionally we can get isLoading
, isError
, error
is we need them.
When we open browser we directly see React Query Devtools. Here we also see all cached paths. In our case only [users]
. We can click on it and check what data is cached. Additionally on the page we successfully fetched data from the API and our component successfully rendered it.
Which actually means that instead of writing useEffect
and managing state on our own we leveraged this library to implement getting data, caching and updating our state.
useMutation
Now we must implement creating of our user. And for sure we want to use React Query for this. For this we must use useMutation
. Mutations allow us to make changes to the API, for example create, update or delete.
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
const Users = () => {
...
const createUserMutation = useMutation(createUser, {
onSuccess: () => {
console.log('onSuccess')
},
});
const [inputValue, setInputValue] = useState("");
const addUser = () => {
createUserMutation.mutate(inputValue);
setInputValue("");
};
...
}
Here we imported useMutation
and useQueryClient
. After this we created a createUserMutation
by calling useMutation
. Inside we provide a promise and options. The only option that we set is onSuccess
which is just a callback after correct mutation.
Now we can call createUserMutation.mutate
anywhere which will call an API.
As you can see in browser we can add a user and our API request is send correctly in network. The only problem is that our list in not being update at all.
Cache invalidation
Mutations don't update our state.
We have our cached path [users]
which was never updated. Our mutation doesn't change this cached data. What is the solution for this? We must invalidate our cache.
For this we can create queryClient
property and call queryClient.invalidateQueries
inside our success callback.
const Users = () => {
const queryClient = useQueryClient();
const { data: users = [] } = useQuery(["users"], getUsers);
const createUserMutation = useMutation(createUser, {
onSuccess: () => {
queryClient.invalidateQueries(["users"]);
},
});
const addUser = () => {
createUserMutation.mutate(inputValue);
setInputValue("");
};
As you can see in browser our list is refetched every single time when we add a user. It happens because we invalidate the query [users]
which triggers refetching of the data.
As you can see the main goal of React Query is to stop synchronising server data with client data plus we get caching and invalidation out of the box.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done