After a while of using Apollo GraphQL and the Apollo react client, you would have come across hooks like useQuery
or useMutation
amongst others.
These hooks can often be nice and convenient to use because it is just a case of plug and play in your react component along with your query or mutation, but what happens when you need to make multiple queries with Apollo useQuery
Well there are a handful of ways you can achieve this which is what we will be going through in this post.
As well as that we will be going over handling Apollo useQuery multiple queries, multiple sequential queries (A query that depends on the one before) with Apollo useQuery, multiple queries in a single query via Apollo useQuery and more!
As always, I will try to avoid as much technical jargon as possible so that everyone of all experience levels can understand and get the most out of this blog post.
Let’s get stuck in!
How to handle multiple queries with the Apollo useQuery hook
First things first, we need to make sure that we have the right packages installed into our project, so let’s go ahead and do this by adding @apollo/client
and graphql-tag
into our package.json by running yarn add @apollo/client
and yarn add graphql-tag
.
Now that we have that installed let’s start by making a basic component that creates a single basic query to our Apollo GraphQL Server.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery} from '@apollo/client';
const BirthdayQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
}
`
export default function ApolloMultiUseQuery() {
const { data, loading, error } = useQuery(BirthdayQuery);
return (
<p>
{data?.birthdays?.date}
</p>
)
}
As you can see, this all looks pretty standard so far, but we need to tackle the problem of multiple queries with Apollo useQuery, so let’s go ahead and add in another query just below.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const BirthdayQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
}
`
const MostPopularCakeQuery = gql`
query mostPopularCakesQuery {
cakes {
id
name
}
}
`
export default function ApolloMultiUseQuery() {
const { data: birthdayData, loading: birthdayLoading, error: birthdayError } = useQuery(BirthdayQuery);
const { data: cakeData, loading: cakeLoading, error: cakeError } = useQuery(MostPopularCakeQuery);
return (
<div>
<p>
{birthdayData?.birthdays?.date}
</p>
{cakeData?.cakes.map(cake => (
<p key={cake.id}>{cake.name}</p>
))}
<button>Refetch</button>
</div>
)
}
For a basic use case that is all you really need to do to incorporate multiple queries with Apollo useQuery.
However, in most cases, things will need to be a little more complex.
For example we need to handle the loading, and error states for our component, and cover cases such as if we want them to load in independently or wait for all to be resolved before loading.
Let’s start by making our component a little easier to use.
One of the biggest difficulties when using multiple Apollo useQuery hooks is that we often destructure the response into const { data, error, loading } = ...
, this is fine for a single query but not for many, so we are going to want to scrap the destructuring here so we can clearly define the response for each.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const BirthdayQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
}
`
const MostPopularCakeQuery = gql`
query mostPopularCakesQuery {
cakes {
id
name
}
}
`
export default function ApolloMultiUseQuery() {
const birthday = useQuery(BirthdayQuery);
const cake = useQuery(MostPopularCakeQuery);
return (
<div>
<p>
{birthday.data?.birthdays?.date}
</p>
{cake.data?.cakes.map(cake => (
<p key={cake.id}>{cake.name}</p>
))}
</div>
)
}
As you can see in the above example it is already looking much more readable and maintainable because we are now naming our responses rather than trying to destructure them.
Next, let’s take a look at handling the errors (assuming that we want to handle them separately)
To do this all we have to do is create some jsx in our React component that looks at both of the error states, and if either requests from the multiple Apollo useQuery have errored then display an error message.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const BirthdayQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
}
`
const MostPopularCakeQuery = gql`
query mostPopularCakesQuery {
cakes {
id
name
}
}
`
export default function ApolloMultiUseQuery() {
const birthday = useQuery(BirthdayQuery);
const cake = useQuery(MostPopularCakeQuery);
const errors = birthday.error || cake.error;
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{birthday.data?.birthdays?.date}
</p>
{cake.data?.cakes.map(cake => (
<p key={cake.id}>{cake.name}</p>
))}
</div>
)
}
And then we do the same for the loading states as well like so:
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const BirthdayQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
}
`
const MostPopularCakeQuery = gql`
query mostPopularCakesQuery {
cakes {
id
name
}
}
`
export default function ApolloMultiUseQuery() {
const birthday = useQuery(BirthdayQuery);
const cake = useQuery(MostPopularCakeQuery);
const errors = birthday.error || cake.error;
const loading = birthday.loading || cake.loading;
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{birthday.data?.birthdays?.date}
</p>
{cake.data?.cakes.map(cake => (
<p key={cake.id}>{cake.name}</p>
))}
</div>
);
}
There we have our Apollo useQuery multiple queries in action independently!
But what if we want to handle them both together?
Well, that brings us onto our next topic which is using a single query object to handle multiple queries for our Apollo GraphQL server.
How to handle multiple queries in a single query via the Apollo useQuery hook
Thanks to the way we create queries in our react applications to Apollo GraphQL servers, we can actually combine queries in a single query string which means that we only need to use one Apollo useQuery hook to perform multiple queries!
This also saves us an extra network request from the client, which is always a good thing.
The only downside to this, is that you won’t be able to perform sequential queries using data from the first, we will get onto this a little more in the next section of this post, but this basically means, if you need the data from the first request to perform the second, you won’t be able to do it with a single query string.
With that said this would be one of the most optimal ways to do this if you can, now let’s look at how we can combine our multiple Apollo useQuery queries into a single query.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const BirthdayAndCakeQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
cakes {
id
name
}
}
`
export default function ApolloMultiUseQuery() {
const { data, loading, error } = useQuery(BirthdayQuery);
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{error && <h3 style={{ color: 'red' }}>{error}</h3>}
<p>
{data?.birthdays?.date}
</p>
{data?.cakes.map(cake => (
<p key={cake.id}>{cake.name}</p>
))}
</div>
);
}
Now that we have covered how to handle multiple queries with Apollo useQuery, both independently and combined, we now need to look at what we can do if we want to handle multiple sequential queries with Apollo useQuery.
How to handle multiple sequential queries with Apollo useQuery?
To start with, what does handling multiple sequential queries with Apollo useQuery mean exactly?
Well, all this means is we need to make two queries to our Apollo GraphQL server in sequence, so one after the other rather than both at the same time.
This might be useful if you need to use some of the data in the response from the first query in the second query.
For example let’s say your first query finds a user by email and returns the ID of that user, then the second query finds the birthday of a user by ID like so:
import gql from 'graphql-tag';
const EmailQuery = gql`
query emailQuery($userEmail: String!) {
user(userEmail: $userEmail) {
id
}
}
`
const UserQuery = gql`
query userQuery($userID: String!) {
birthday(userID: $userID) {
birthday
formatted
}
}
`
In this example you need to get the ID first so you can then use it to find the birthday in the second query.
A quick note about this example, this is only demonstrating a case for this post but generally, if you are using Apollo GraphQL in this situation then you would expect the first query to also return the birthday and you wouldn’t make two requests here.
Now, it goes without saying that if you are in this situation and you are using Apollo GraphQL, then you might need to refactor and re-think your GraphQL schema so that you don’t need to do multiple sequential queries in the front-end/client of your application.
Apollo GraphQL uses a graph to connect your data, so in this situation it would be better to find the user, and then resolve the user in your GraphQL server so that the front-end/client can just query the birthday in the original query.
So now the above queries when optimized would look more like:
import gql from 'graphql-tag';
const UserQuery = gql`
query userQuery($userEmail: String!) {
findUser(userEmail: $userEmail) {
id
email
birthday
formatted
}
}
`
Think of having to do multiple sequential Apollo useQuery hooks as a last resort if for some reason you cannot resolve the entity in your server, or maybe you are using two different graphs that you cannot combine for one reason or another.
With that said let’s take a look at how we can handle multiple sequential queries with Apollo useQuery.
Firstly, let’s start by taking our example from before with the two independent queries but with the user birthday example.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const EmailQuery = gql`
query emailQuery($userEmail: String!) {
user(userEmail: $userEmail) {
id
name
}
}
`
const UserQuery = gql`
query userQuery($userID: String!) {
birthday(userID: $userID) {
birthday
formatted
}
}
`
export default function ApolloMultiUseQuery({ userEmail }) {
const user = useQuery(EmailQuery);
const birthday = useQuery(UserQuery);
const errors = user.error || birthday.error;
const loading = user.loading || birthday.loading;
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{user.data?.user?.name}
</p>
<p>
{birthday.data?.birthday?.formatted}
</p>
</div>
);
}
Now we need to add in our variables into the useQuery hooks and into the GraphQL query strings so that we can send it to our backend service.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const EmailQuery = gql`
query emailQuery($userEmail: String!) {
user(userEmail: $userEmail) {
id
name
}
}
`
const UserQuery = gql`
query userQuery($userID: String!) {
birthday(userID: $userID) {
birthday
formatted
}
}
`
export default function ApolloMultiUseQuery({ userEmail }) {
const user = useQuery(EmailQuery, {
variables: { userEmail }
});
const birthday = useQuery(UserQuery, {
variables: { userID: email.data?.user?.id }
});
const errors = user.error || birthday.error;
const loading = user.loading || birthday.loading;
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{user.data?.user?.name}
</p>
<p>
{birthday.data?.birthday?.formatted}
</p>
</div>
);
}
Now as you can see there is a small problem here.
When this component is rendered, both queries will be sent at the same time meaning the second will not have the variables it needs which will cause an error.
To prevent this, we need to make use of the skip
property in the second useQuery hook.
Setting the skip
field to true
if the variable does not exist will mean the query won’t be fired until we have the data (ID in this example) from the first query.
This is the key to how we can make multiple sequential Apollo useQuery requests work in our React client.
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const EmailQuery = gql`
query emailQuery($userEmail: String!) {
user(userEmail: $userEmail) {
id
name
}
}
`
const UserQuery = gql`
query userQuery($userID: String!) {
birthday(userID: $userID) {
birthday
formatted
}
}
`
export default function ApolloMultiUseQuery({ userEmail }) {
const user = useQuery(EmailQuery, {
variables: { userEmail },
skip: !userEmail
});
const birthday = useQuery(UserQuery, {
variables: { userID: user.data?.user?.id },
skip: user.loading || !user.data?.user?.id
});
const errors = user.error || birthday.error;
const loading = user.loading || birthday.loading;
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{user.data?.user?.name}
</p>
<p>
{birthday.data?.birthday?.formatted}
</p>
</div>
);
}
And as you can see this gives us our multiple sequential queries with Apollo useQuery hooks using data from the first in the second.
When the first query finishes loading in the data, the skip
property will be set to true
because of the variable we are using which will then allow the second query to be fired.
How to use refetch with Apollo useQuery multiple queries
The last thing we need to cover in this post is how you can combine these queries with the refetch function.
Refetching is pretty much what it says on the tin.
It lets you re-run your queries to fetch any new data that might have come in, you may want to do this if you want to show the latest data without having to manually feed in another data source.
Using just re-fetch with the same queries as before makes life much simpler!
Now, we have a few scenarios here that could produce some edge-cases that become quite confusing.
Firstly we need to look at a standard multiple Apollo useQuery refetch (non-sequential).
This one is pretty straightforward; luckily all we need to do here is combine the two refetches into a single function that calls both.
Now because these are not sequential and we are relying on the data from the useQuery hook, we can call them both at the same time without having to create an async function that waits for them to complete.
An important detail here before we look at the example is that we need to add in notifyOnNetworkStatusChange: true
to the options of the query.
We need to set notifyOnNetworkStatusChange
to true
so that the loading state will update as expected when we trigger the refetch, just like on the original query.
Without it, even though the data, and error states would update, the loading state would not.
This is fine if you don’t want to show the data is loading in, but in this example we do.
Let’s look at a quick example of how to do this:
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const BirthdayQuery = gql`
query birthdayQuery {
birthdays {
id
date
}
}
`
const MostPopularCakeQuery = gql`
query mostPopularCakesQuery {
cakes {
id
name
}
}
`
export default function ApolloMultiUseQuery() {
const birthday = useQuery(BirthdayQuery, {
notifyOnNetworkStatusChange: true
});
const cake = useQuery(MostPopularCakeQuery, {
notifyOnNetworkStatusChange: true
});
const errors = birthday.error || cake.error;
const loading = birthday.loading || cake.loading;
const refetchAll = () => {
user.refetch();
birthday.refetch();
};
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{birthday.data?.birthdays?.date}
</p>
{cake.data?.cakes.map(cake => (
<p key={cake.id}>{cake.name}</p>
))}
<button onClick={refetchAll}>Refetch</button>
</div>
);
}
Now let’s take a look at multiple sequential Apollo useQuery retches.
This is where it becomes a little more tricky (but not too much), we now need to wait for the first refetch call before we can trigger the second.
There are many reasons why you might need to do this, but if you are using the exact same values as the initial requests, then you might not need to worry and can just treat this as non-sequential multiple useQuery queries.
However, if you do need to then all we need to do is to update our function to wait for the first query before triggering the second, this can be achieved pretty easily with async
and await
.
Let’s take a look at how we can do this:
import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from '@apollo/client';
const EmailQuery = gql`
query emailQuery($userEmail: String!) {
user(userEmail: $userEmail) {
id
name
}
}
`
const UserQuery = gql`
query userQuery($userID: String!) {
birthday(userID: $userID) {
birthday
formatted
}
}
`
export default function ApolloMultiUseQuery({ userEmail }) {
const user = useQuery(EmailQuery, {
variables: { userEmail },
skip: !userEmail,
notifyOnNetworkStatusChange: true
});
const birthday = useQuery(UserQuery, {
variables: { userID: user.data?.user?.id },
skip: user.loading || !user.data?.user?.id,
notifyOnNetworkStatusChange: true
});
const errors = user.error || birthday.error;
const loading = user.loading || birthday.loading;
const refetchAll = async () => {
await user.refetch();
await birthday.refetch();
};
if (loading) {
return <p>loading...</p>;
}
return (
<div>
{errors && <h3 style={{ color: 'red' }}>{errors}</h3>}
<p>
{user.data?.user?.name}
</p>
<p>
{birthday.data?.birthday?.formatted}
</p>
<button onClick={refetchAll}>Refetch</button>
</div>
);
}
And there we have it!
Summary
Now we have covered almost everything to do with how to use Apollo useQuery with multiple queries!
Just as a last note, it is best to try to avoid having to use multiple Apollo useQuery hooks wherever possible.
If this situation does arise then it means it might be worth looking at your Apollo GraphQL schema and changing it where possible so you don’t have to use multiple hooks.
Be sure to check out some of my other blog posts!