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:
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`;
1import gql from 'graphql-tag';
2import { Query } from 'react-apollo';
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:
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}
1const Dogs = ({ onDogSelected }) => (
2 <Query query={GET_DOGS}>
3 {({ loading, error, data }) => {
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 }}
17 </Query>
18);
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
istrue
(indicating the query is still in flight), the component presents aLoading...
notice.When loading is
false
and there is noerror
, 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:
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}
1const GET_DOG_PHOTO = gql`
2 query Dog($breed: String!) {
3 dog(breed: $breed) {
4 id
5 displayImage
6 }
7 }
8`;
9
10const DogPhoto = ({ breed }) => (
11 <Query query={GET_DOG_PHOTO} variables={{ breed }}>
12 {({ loading, error, data }) => {
13 if (loading) return null;
14 if (error) return `Error! ${error}`;
15
16 return (
17 <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
18 );
19 }}
20 </Query>
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:
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}
1const DogPhoto = ({ breed }) => (
2 <Query
3 query={GET_DOG_PHOTO}
4 variables={{ breed }}
5 skip={!breed}
6 pollInterval={500}
7 >
8 {({ loading, error, data }) => {
9 if (loading) return null;
10 if (error) return `Error! ${error}`;
11
12 return (
13 <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
14 );
15 }}
16 </Query>
17);
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
andstopPolling
functions that are returned by theuseQuery
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.
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}
1const DogPhoto = ({ breed }) => (
2 <Query query={GET_DOG_PHOTO} variables={{ breed }} skip={!breed}>
3 {({ loading, error, data, refetch }) => {
4 if (loading) return null;
5 if (error) return `Error! ${error}`;
6
7 return (
8 <div>
9 <img
10 src={data.dog.displayImage}
11 style={{ height: 100, width: 100 }}
12 />
13 <button onClick={() => refetch()}>Refetch!</button>
14 </div>
15 );
16 }}
17 </Query>
18);
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:
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 (