Docs
Launch GraphOS Studio

Queries

Fetch data with the useQuery hook


This article shows how to fetch data in React with the useQuery hook and attach the result to your UI. You'll also learn how simplifies data management code by tracking error and loading states for you.

Prerequisites

This article assumes you're familiar with building basic queries. If you need a refresher, we recommend this guide. You can also build example queries against Apollo's full-stack tutorial server.

This article also assumes that you've already set up and have wrapped your React app in an ApolloProvider component. For more information, see the getting started guide.

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 within a React component, call useQuery and pass it a string. When your component renders, useQuery returns an object from that contains loading, error, and data properties you can use to render your UI.

Note: in >= 3.8, Suspense data fetching hooks are available for data within <Suspense /> boundaries using React 18's new concurrent rendering model. For more information see 's Suspense docs.

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

index.js
import { gql, useQuery } from '@apollo/client';
const GET_DOGS = gql`
query GetDogs {
dogs {
id
breed
}
}
`;

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

index.js
function Dogs({ onDogSelected }) {
const { loading, error, data } = useQuery(GET_DOGS);
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<select name='dog' onChange={onDogSelected}>
{data.dogs.map((dog) => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}

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

  • As long as loading is true (indicating the is still in flight), the component presents a Loading... notice.
  • When loading is false and there is no error, the 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 that uses .

Caching query results

Whenever fetches results from your server, it automatically caches those results locally. This makes later executions of that same 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:

index.js
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}

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 we want to pass to our . 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 cache at work!

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

Updating cached query results

Sometimes, you want to make sure that your 's cached data is up to date with your server's data. supports two strategies for this: polling and refetching.

Polling

Polling provides near-real-time synchronization with your server by executing your 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:

index.js
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
pollInterval: 500,
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
);
}

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

You can also start and stop polling dynamically with the startPolling and stopPolling functions that are returned by the useQuery hook. When using these functions, set the pollInterval configuration option as a parameter of the startPolling function.

Refetching

Refetching enables you to refresh 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 's refetch function whenever it's clicked.

You can optionally provide a new variables object to the refetch function. If you avoid passing a variables object and use only refetch(), the uses the same variables that it used in its previous execution.

index.js
function DogPhoto({ breed }) {
const { loading, error, data, refetch } = useQuery(GET_DOG_PHOTO, {
variables: { breed },
});
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<div>
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
<button onClick={() => refetch()}>
Refetch new breed!
</button>
</div>
);
}

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.

Providing new variables to refetch

You call refetch with a new set of like so:

<button
onClick={() =>
refetch({
breed: 'dalmatian', // Always refetches a dalmatian instead of original breed
})
}
>
Refetch!
</button>

If you provide new values for some of your original 's but not all of them, refetch uses each omitted 's original value.

Inspecting loading states

We've already seen that the useQuery hook exposes our '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?

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

index.js
import { NetworkStatus } from '@apollo/client';
function DogPhoto({ breed }) {
const { loading, error, data, refetch, networkStatus } = useQuery(
GET_DOG_PHOTO,
{
variables: { breed },
notifyOnNetworkStatusChange: true,
}
);
if (networkStatus === NetworkStatus.refetch) return 'Refetching!';
if (loading) return null;
if (error) return `Error! ${error}`;
return (
<div>
<img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
<button onClick={() => refetch()}>
Refetch!
</button>
</div>
);
}

Enabling this option also ensures that the value of loading updates accordingly, even if you don't want to use the more fine-grained information provided by the networkStatus property.

The networkStatus property is a NetworkStatus enum that represents different loading states. Refetch is represented by NetworkStatus.refetch, and there are also values for polling and pagination. For a full list of all the possible loading states, check out the source.

To view a complete version of the app we just built, check out the CodeSandbox here.

Inspecting error states

You can customize your error handling by providing the errorPolicy configuration option to the useQuery hook. The default value is none, which tells to treat all errors as runtime errors. In this case, Apollo Client discards any response data returned by the server and sets the error property in the useQuery result object.

If you set errorPolicy to all, useQuery does not discard response data, allowing you to render partial results.

For more information, see Handling operation errors.

Manual execution with useLazyQuery

When React renders a component that calls useQuery, automatically executes the corresponding . But what if you want to execute a query in response to a different event, such as a user clicking a button?

The useLazyQuery hook is perfect for executing queries in response to events besides component rendering. Unlike with useQuery, when you call useLazyQuery, it does not immediately execute its associated . Instead, it returns a query function in its result tuple that you call whenever you're ready to execute the .

Here's an example:

index.js
import React from 'react';
import { useLazyQuery } from '@apollo/client';
function DelayedQuery() {
const [getDog, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
if (error) return `Error! ${error}`;
return (
<div>
{data?.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>
Click me!
</button>
</div>
);
}

The first item in useLazyQuery's return tuple is the function, and the second item is the same result object returned by useQuery.

As shown above, you can pass options to the function just like you pass them to useLazyQuery itself. If you pass a particular option to both, the value you pass to the function takes precedence. This is a handy way to pass default options to useLazyQuery and then customize those options in the function.

NOTE

are merged by taking the variables passed as options to the hook and merging them with the variables passed to the function. If you do not pass variables to the function, only the variables passed to the hook are used in the execution.

For a full list of supported options, see the API reference.

Setting a fetch policy

By default, the useQuery hook checks the cache to see if all the data you requested is already available locally. If all data is available locally, useQuery returns that data and doesn't your . This cache-first policy is 's default fetch policy.

You can specify a different fetch policy for a given . To do so, include the fetchPolicy option in your call to useQuery:

const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: 'network-only', // Doesn't check cache before making a network request
});

nextFetchPolicy
Since 3.1

You can also specify a 's nextFetchPolicy. If you do, fetchPolicy is used for the 's first execution, and nextFetchPolicy is used to determine how the responds to future cache updates:

const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: 'network-only', // Used for first execution
nextFetchPolicy: 'cache-first', // Used for subsequent executions
});

For example, this is helpful if you want a to always make an initial network request, but you're comfortable reading from the cache after that.

nextFetchPolicy functions

If you want to apply a single nextFetchPolicy by default, because you find yourself manually providing nextFetchPolicy for most of your queries, you can configure defaultOptions.watchQuery.nextFetchPolicy when creating your ApolloClient instance:

new ApolloClient({
link,
client,
defaultOptions: {
watchQuery: {
nextFetchPolicy: 'cache-only',
},
},
});

This configuration applies to all client.watchQuery calls and useQuery calls that do not otherwise configure nextFetchPolicy.

If you want more control over how nextFetchPolicy behaves, you can provide a function instead of a WatchQueryFetchPolicy string:

new ApolloClient({
link,
client,
defaultOptions: {
watchQuery: {
nextFetchPolicy(currentFetchPolicy) {
if (
currentFetchPolicy === 'network-only' ||
currentFetchPolicy === 'cache-and-network'
) {
// Demote the network policies (except "no-cache") to "cache-first"
// after the first request.
return 'cache-first';
}
// Leave all other fetch policies unchanged.
return currentFetchPolicy;
},
},
},
});

This nextFetchPolicy function will be called after each request, and uses the currentFetchPolicy parameter to decide how to modify the fetch policy.

In addition to being called after each request, your nextFetchPolicy function will also be called when change, which by default resets the fetchPolicy to its initial value, which is often important to trigger a fresh network request for queries that started out with cache-and-network or network-only fetch policies.

To intercept and handle the variables-changed case yourself, you can use the NextFetchPolicyContext object passed as the second to your nextFetchPolicy function:

new ApolloClient({
link,
client,
defaultOptions: {
watchQuery: {
nextFetchPolicy(
currentFetchPolicy,
{
// Either "after-fetch" or "variables-changed", indicating why the
// nextFetchPolicy function was invoked.
reason,
// The rest of the options (currentFetchPolicy === options.fetchPolicy).
options,
// The original value of options.fetchPolicy, before nextFetchPolicy was
// applied for the first time.
initialPolicy,
// The ObservableQuery associated with this client.watchQuery call.
observable,
}
) {
// When variables change, the default behavior is to reset
// options.fetchPolicy to context.initialPolicy. If you omit this logic,
// your nextFetchPolicy function can override this default behavior to
// prevent options.fetchPolicy from changing in this case.
if (reason === 'variables-changed') {
return initialPolicy;
}
if (
currentFetchPolicy === 'network-only' ||
currentFetchPolicy === 'cache-and-network'
) {
// Demote the network policies (except "no-cache") to "cache-first"
// after the first request.
return 'cache-first';
}
// Leave all other fetch policies unchanged.
return currentFetchPolicy;
},
},
},
});

In order to debug these nextFetchPolicy transitions, it can be useful to add console.log or debugger statements to the function body, to see when and why the function is called.

Supported fetch policies

NameDescription
cache-first

first executes the against the cache. If all requested data is present in the cache, that data is returned. Otherwise, executes the against your and returns that data after caching it.

Prioritizes minimizing the number of network requests sent by your application.

This is the default fetch policy.

cache-only

executes the only against the cache. It never queries your server in this case.

A cache-only throws an error if the cache does not contain data for all requested .

cache-and-network

executes the full against both the cache and your . The automatically updates if the result of the server-side query modifies cached .

Provides a fast response while also helping to keep cached data consistent with server data.

network-only

executes the full against your , without first checking the cache. The 's result is stored in the cache.

Prioritizes consistency with server data, but can't provide a near-instantaneous response when cached data is available.

no-cache

Similar to network-only, except the 's result is not stored in the cache.

standby

Uses the same logic as cache-first, except this does not automatically update when underlying values change. You can still manually update this with refetch and updateQueries.

useQuery API

Supported options and result for the useQuery hook are listed below.

Most calls to useQuery can omit the majority of these options, but it's useful to know they exist. To learn about the useQuery hook API in more detail with usage examples, see the API reference.

Options

The useQuery hook accepts the following options:

Operation options

client (optional)

ApolloClient<any>

The instance of ApolloClient to use to execute the .

By default, the instance that's passed down via context is used, but you can provide a different instance here.

Specifies how the handles a response that returns both errors and partial results.

For details, see GraphQL error policies.

The default value is none, meaning that the result includes error details but not partial results.

(data: TData) => void

A callback function that's called when your successfully completes with zero errors (or if errorPolicy is ignore and partial data is returned).

This function is passed the 's result data.

(error: ApolloError) => void

A callback function that's called when the encounters one or more errors (unless errorPolicy is ignore).

This function is passed an ApolloError object that contains either a networkError object or a graphQLErrors array, depending on the error(s) that occurred.

If true, the is not executed.

The default value is false.

An object containing all of the your requires to execute.

Each key in the object corresponds to a name, and that key's value corresponds to the variable value.

Networking options
DefaultContext

If you're using Apollo Link, this object is the initial value of the context object that's passed along your link chain.

If true, the in-progress 's associated component re-renders whenever the network status changes or a network error occurs.

The default value is false.

Specifies the interval (in milliseconds) at which the polls for updated results.

The default value is 0 (no polling).

A callback function that's called whenever a refetch attempt occurs while polling. If the function returns true, the refetch is skipped and not reattempted until the next poll interval.

Pass false to skip executing the during server-side rendering.

Caching options
WatchQueryFetchPolicy

Specifies how the interacts with the cache during execution (for example, whether it checks the cache for results before sending a request to the server).

For details, see Setting a fetch policy.

The default value is cache-first.

WatchQueryFetchPolicy

Defaults to the initial value of options.fetchPolicy, but can be explicitly configured to specify the WatchFetchPolicy to revert back to whenever change (unless nextFetchPolicy intervenes).

WatchQueryFetchPolicy | ((this: WatchQueryOptions<TVariables, TData>, currentFetchPolicy: WatchQueryFetchPolicy, context: NextFetchPolicyContext<TData, TVariables>) => WatchQueryFetchPolicy)

Specifies the FetchPolicy to be used after this has completed.

Specifies whether a NetworkStatus.refetch should merge incoming data with existing data, or overwrite the existing data. Overwriting is probably preferable, but merging is currently the default behavior, for backwards compatibility with 3.x.

If true, the can return partial results from the cache if the cache doesn't contain results for all queried .

The default value is false.

Other

⚠️ Deprecated

Using canonizeResults can result in memory leaks so we generally do not recommend using this option anymore. A future version of will contain a similar feature without the risk of memory leaks.

Whether to canonize cache results before returning them. Canonization takes some extra time, but it speeds up future deep equality comparisons. Defaults to false.

⚠️ Deprecated

Setting this option is unnecessary in 3, thanks to a more consistent application of fetch policies. It might be removed in a future release.

If true, causes a refetch if the query result is detected as partial.

The default value is false.

Result

After being called, the useQuery hook returns a result object with the following properties. This object contains your result, plus some helpful functions for refetching, dynamic polling, and pagination.

Operation data
TData | undefined

An object containing the result of your after it completes.

This value might be undefined if a results in one or more errors (depending on the query's errorPolicy).

ApolloError

If the produces one or more errors, this object contains either an array of graphQLErrors or a single networkError. Otherwise, this value is undefined.

For more information, see Handling operation errors.

An object containing the result from the most recent previous execution of this .

This value is undefined if this is the 's first execution.

TVariables | undefined

An object containing the that were provided for the .

Network info
boolean

If true, the associated lazy has been executed.

This is only present on the result object returned by useLazyQuery.

ApolloClient<any>

The instance of that executed the . Can be useful for manually executing followup queries or writing data to the cache.

boolean

If true, the is still in flight and results have not yet been returned.

NetworkStatus

A number indicating the current network state of the 's associated request. See possible values.

Used in conjunction with the notifyOnNetworkStatusChange option.

Helper functions
<TFetchData = TData, TFetchVars extends OperationVariables = TVariables>(fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & { updateQuery?: (previousQueryResult: TData, options: { fetchMoreResult: TFetchData; variables: TFetchVars; }) => TData; }) => Promise<ApolloQueryResult<TFetchData>>

A function that helps you fetch the next set of results for a paginated list field.

(variables?: Partial<TVariables>) => Promise<ApolloQueryResult<TData>>

A function that enables you to re-execute the , optionally passing in new variables.

To guarantee that the refetch performs a network request, its fetchPolicy is set to network-only (unless the original 's fetchPolicy is no-cache or cache-and-network, which also guarantee a network request).

See also Refetching.

(pollInterval: number) => void

A function that instructs the to begin re-executing at a specified interval (in milliseconds).

() => void

A function that instructs the to stop polling after a previous call to startPolling.

<TSubscriptionData = TData, TSubscriptionVariables extends OperationVariables = TVariables>(options: SubscribeToMoreOptions<TData, TSubscriptionVariables, TSubscriptionData>) => () => void

A function that enables you to execute a subscription, usually to subscribe to specific that were included in the .

This function returns another function that you can call to terminate the .

<TVars extends OperationVariables = TVariables>(mapFn: (previousQueryResult: TData, options: Pick<WatchQueryOptions<TVars, TData>, "variables">) => TData) => void

A function that enables you to update the 's cached result without executing a followup .

See using updateQuery and updateFragment for additional information.

Other
ObservableQuery<TData, TVariables>

A reference to the internal ObservableQuery used by the hook.

Next steps

Now that you understand how to fetch data with the useQuery hook, learn how to update your data with the useMutation hook!

After that, learn about some other handy features:

Previous
Get started
Next
Suspense
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company