Queries

Learn how to fetch data with the useQuery hook


Fetching data in a simple, predictable way is one of the core features of Apollo Client. This article demonstrates how to fetch GraphQL data in React with the useQuery hook and attach the result to your UI. You'll also learn how Apollo Client simplifies data management code by tracking error and loading states for you.

Prerequisites

This article assumes you're familiar with building basic GraphQL queries. If you need a refresher, we recommend that you read this guide and practice running queries in GraphiQL. Apollo Client queries are standard GraphQL, so any query that runs in GraphiQL will also run when provided to useQuery.

This article also assumes that you've already set up Apollo Client and have wrapped your React app in an ApolloProvider component. Read our getting started guide if you need help with either of those steps.

To follow along with the examples below, open up our starter project and sample GraphQL server on CodeSandbox. You can view the completed version of the app here.

Executing a query

The useQuery React hook is the primary API for executing queries in an Apollo application. To run a query within a React component, call useQuery and pass it a GraphQL query string. When your component renders, useQuery returns an object from Apollo Client that contains loading, error, and data properties you can use to render your UI.

Let's look at an example. First, we'll create a GraphQL query named GET_DOGS. Remember to wrap query strings in the gql function to parse them into query documents:

TypeScript
TypeScript
index.js(hooks)
1import gql from 'graphql-tag';
2import { useQuery } from '@apollo/react-hooks';
3
4const GET_DOGS = gql`
5  {
6    dogs {
7      id
8      breed
9    }
10  }
11`;

Next, we'll create a component named Dogs. Inside it, we'll pass our GET_DOGS query to the useQuery hook:

TypeScript
TypeScript
index.js(hooks)
1function Dogs({ onDogSelected }) {
2  const { loading, error, data } = useQuery(GET_DOGS);
3
4  if (loading) return 'Loading...';
5  if (error) return `Error! ${error.message}`;
6
7  return (
8    <select name="dog" onChange={onDogSelected}>
9      {data.dogs.map(dog => (
10        <option key={dog.id} value={dog.breed}>
11          {dog.breed}
12        </option>
13      ))}
14    </select>
15  );
16}

As our query executes and the values of loading, error, and data change, the Dogs component can intelligently render different UI elements according to the query's state:

  • As long as loading is true (indicating the query is still in flight), the component presents a Loading... notice.

  • When loading is false and there is no error, the query has completed. The component renders a dropdown menu that's populated with the list of dog breeds returned by the server.

When the user selects a dog breed from the populated dropdown, the selection is sent to the parent component via the provided onDogSelected function.

In the next step, we'll associate the dropdown with a more sophisticated query that uses GraphQL variables.

Caching query results

Whenever Apollo Client fetches query results from your server, it automatically caches those results locally. This makes subsequent executions of the same query extremely fast.

To see this caching in action, let's build a new component called DogPhoto. DogPhoto accepts a prop called breed that reflects the current value of the dropdown menu in our Dogs component:

JavaScript
JavaScript
index.js(hooks)
1const GET_DOG_PHOTO = gql`
2  query Dog($breed: String!) {
3    dog(breed: $breed) {
4      id
5      displayImage
6    }
7  }
8`;
9
10function DogPhoto({ breed }) {
11  const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
12    variables: { breed },
13  });
14
15  if (loading) return null;
16  if (error) return `Error! ${error}`;
17
18  return (
19    <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
20  );
21}

Notice that we're providing a configuration option (variables) to the useQuery hook this time. The variables option is an object that contains all of the variables we want to pass to our GraphQL query. In this case, we want to pass the currently selected breed from the dropdown.

Select bulldog from the dropdown to see its photo appear. Then switch to another breed, and then switch back to bulldog. You'll notice that the bulldog photo loads instantly the second time around. This is the Apollo cache at work!

Next, let's learn some techniques for ensuring that our cached data is fresh.

Updating cached query results

Caching query results is handy and easy to do, but sometimes you want to make sure that cached data is up to date with your server. Apollo Client supports two strategies for this: polling and refetching.

Polling

Polling provides near-real-time synchronization with your server by causing a query to execute periodically at a specified interval. To enable polling for a query, pass a pollInterval configuration option to the useQuery hook with an interval in milliseconds:

JavaScript
JavaScript
index.js(hooks)
1function DogPhoto({ breed }) {
2  const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
3    variables: { breed },
4    skip: !breed,
5    pollInterval: 500,
6  });
7
8  if (loading) return null;
9  if (error) return `Error! ${error}`;
10
11  return (
12    <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
13  );
14}

By setting the pollInterval to 500, you'll fetch the current breed's image from the server every 0.5 seconds. Note that if you set pollInterval to 0, the query will not poll.

You can also start and stop polling dynamically with the startPolling and stopPolling functions that are returned by the useQuery hook.

Refetching

Refetching enables you to refresh query results in response to a particular user action, as opposed to using a fixed interval.

Let's add a button to our DogPhoto component that calls our query's refetch function whenever it's clicked.

You can optionally provide a new variables object to the refetch function. If you don't (as is the case in the following example), the query uses the same variables that it used in its previous execution.

JavaScript
JavaScript
index.js(hooks)
1function DogPhoto({ breed }) {
2  const { loading, error, data, refetch } = useQuery(GET_DOG_PHOTO, {
3    variables: { breed },
4    skip: !breed,
5  });
6
7  if (loading) return null;
8  if (error) return `Error! ${error}`;
9
10  return (
11    <div>
12      <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
13      <button onClick={() => refetch()}>Refetch!</button>
14    </div>
15  );
16}

Click the button and notice that the UI updates with a new dog photo. Refetching is an excellent way to guarantee fresh data, but it introduces some complexity with loading state. In the next section, we'll cover strategies for handling complex loading and error state.

Inspecting loading states

We've already seen that the useQuery hook exposes our query's current loading state. This is helpful when a query first loads, but what happens to our loading state when we're refetching or polling?

Let's return to our refetching example from the previous section. If you click the refetch button, you'll see that the component doesn't re-render until the new data arrives. What if we want to indicate to the user that we're refetching the photo?

Luckily, the useQuery hook's result object provides fine-grained information about the status of the query via the networkStatus property. To take advantage of this information, we need to set the notifyOnNetworkStatusChange option to true so our query component re-renders while a refetch is in flight:

JavaScript
JavaScript
index.js(hooks)
1function DogPhoto({ breed }) {
2  const { loading, error, data, refetch, networkStatus } = useQuery(
3    GET_DOG_PHOTO,
4    {
5      variables: { breed },
6      skip: !breed,
7      notifyOnNetworkStatusChange: true,
8    },
9  );
10
11  if (