A beginner’s guide to react query (Tanstack V5), part 3: The useQuery hook.

Emmanuel Nnajiofor
5 min readJul 29, 2024

--

This series has been a walkthrough of the different components and usages of react-query. For the earlier parts, we have discussed in detail the core building blocks and setup for react-query and the query client.

Photo by Pavan Trikutam on Unsplash

In this episode, we will be taking a deep dive into the useQuery hook and how we can use this alongside Axios to handle API calls in our application.

This is the hook provided by react-query that typically handles Get requests to an API endpoint, allowing us to listen on asynchronous events such as isLoading , isSuccess , isError , isFetching .

The useQuery hook is configured with an object that receives certain parameters describing the exact query function and the behavior of the query.

Let’s take a moment to look at some of the values of this object.

const {...} = useQuery({
queryKey,
queryFn,
enabled,
select
})

If you read my previous article on the earlier version of this, you will notice that a few things seem to be missing here. This is because commonly used callback events like onError , onSuccess and onSettled have been removed in this version. Curious enough to read a detailed post about why they did this?. Check it out here.

While the names of most of these options are self-explanatory, let’s discuss them however.

queryKey : A unique identifier for the query. Query keys must be an array at the top level. They can range from a single string in an array to a complex array containing multiple strings and nested objects. As long as the query key is serializable and unique to the query’s data, it is valid to use.

useQuery({ queryKey: ['todos'] })

The querykey can receive a simple array of strings like this but may also include variables like those below

useQuery({ queryKey: ['todos', page, status] })

In scenarios where our queryFunction which we would see shortly receives an argument, we need to add that argument as part of the items in our query key. This is because query keys act as dependencies for query functions, adding dependent variables to the query key will ensure that queries are cached independently and that any time a variable changes, queries will be refetched automatically.

const result = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
})

queryFn : A required function that handles the request. The function must return a promise that resolves data or throws an error. The data cannot be undefined .

//A sample request with axios
const fetchData = async () => {
const response = await axios.get('/api/data-endpoint');
if (!response.data) {
throw new Error('Data is undefined');
}
return response.data;
};

const MyComponent = () => {
const {...} = useQuery({
queryKey: ['dataKey'],
queryFn: fetchData,
});

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

return (
<>
//render data within component here.
</>
);
};

In the above snippet, we use Axios to make a demo request and pass the function as the value for our queryFn callback in the useQuery hook.

enabled : The enabled parameter is a boolean that allows us to disable the query from automatically running.

const MyComponent = ({ todoId }) => {
const {...} = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
enabled: !!todoId
});

...
};

In the above snippet, we disable the query from running automatically by using the !! syntax to make the todoId act as a boolean value.

select : This option allows you to transform or select a portion of the data returned by the query function. It modifies the data that is returned but does not change what is stored in the query cache.

const MyComponent = ({ todoId }) => {
const {...} = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetchTodoById(todoId),
selecte: (data: TData) => {todos: data.items, count: data.items.length}
enabled: !!todoId
});

...
};

All other parameters on the object like refetchIntervalInBackground ,refetchOnMount, refetchOnReconnect, refetchOnWindowFocus, retry, retryOnMount, retryDelay and refetchInterval that are usually configured on the queryClient and were discussed here, can also be configured here as well for individual queries. This allows us fine-tune the behaviour of a unique query.

So far we have discussed the values of the object received by the useQuery hook. Now, we will talk about a second argument that was introduced in this version (V5).

const { ... } = useQuery({
...
},
queryClient,
)

The useQuery hook now accepts an optional unique queryClient different from the one in the nearest context. This can be useful if we need to define a set of configurations for a group of queries that we want to behave in a particular way.

At this point, we understand all we need to configure the useQuery hook and fine-tune our queries to get the exact behaviour that we want.

The hook returns an object containing the data from the query and other information including the different states of the query, errors, and callbacks.

const {
data,
error,
isError,
isFetching,
isLoading,
isRefetching,
isPending,
isStale,
isSuccess,
refetch,
status,
} = useQuery({...})

data : The data returned from the query. If the query is still loading or has failed, this will be undefined until the query is successful. This is the value that allows us access fetched data.

error : The error object returned from the query if it fails. Can be used to display error messages or handle query failures.

refetch : The hook returns this value that allows us manually refetch the query. Useful for refreshing data in response to user actions or other events.

status : The current status of the query. Can be loading, error, or success. This is helpful in controlling UI states and flow based on the overall query status.

The status of the query can also be checked via booleans returned by the hook. isError , isSuccess , isLoading , isFetching , isRefetchingand isPending

isStale : Indicates whether the query data is considered stale. We can use this to trigger a refetch.

A variation of the useQuery Hook exists as useSuspenseQuery .

It is slightly different from useQuery except that it does not receive the error prop and in the returned object ; data is guaranteed to be defined and status is always success.

Now that we have covered the useQuery hook, you have all that you would need to use this tool within your react app to handle API-requests efficiently.

In the next part, we’ll be looking at how to make POST, PUT, DEL, and PATCH requests with mutations via the useMutation hook. See you on the next one.

--

--