Blog / React

How to use Apollo useQuery with multiple queries

How to use Apollo useQuery with multiple queries

Learn how to use Apollo useQuery with multiple queries and sequential queries in your react app using Apollo client with Apollo GraphQL.

Will MaygerWill Mayger
June 28, 2021
Article

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>
  )
}
Basic component using multiple Apollo useQuery hooks example in react

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>
  )
}
Basic component using multiple Apollo useQuery hooks with error state example in react

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>
  );
}
Basic component using multiple Apollo useQuery hooks with loading state example in react

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!

Foxi - Budget Planner & Tracker

Foxi

Budget Planner & Tracker

More money in your pocket by the end of the month.

Free to use and no account needed.

Get started now.

Get the app

Some graphics used on this post were made using icons from flaticon.

Latest Posts

Learn React, JavaScript and TypeScript

Learn React, JavaScript and TypeScript

Join the platform that top tier companies are using.
Master the skills you need to succeed as a software engineer and take your career to the next level with Pluralsight.

Start here

Become an expert in ReactJS, TypeScript, and JavaScript.

Here you will find my personal recomendations to you, for full disclosure I earn a small commission from some of these links, but I only recommend what I trust and personally use.

Good things are coming, don't miss out!

Good things are coming, don't miss out!

Follow me on Twitter to stay up to date and learn frontend, React, JavaScript, and TypeScript tips and tricks!

Are you a novice, intermediate or expert react engineer?

Find out here by taking my fun, interactive, quick quiz which takes approximately 1 - 3 minutes. How well will you will do?

Foxi - Budget Planner & Tracker

Foxi

Budget Planner & Tracker

More money in your pocket by the end of the month.

Free to use and no account needed.

Get started now.

Get the app