@apollo/client
Version:
A fully-featured caching GraphQL client.
417 lines (327 loc) • 10.3 kB
Markdown
# Queries Reference
> **Note**: In most applications, there should only be one use of a query hook per page. Use fragment-reading hooks (`useFragment`, `useSuspenseFragment`) with component-colocated fragments and data masking for the rest of the page components.
## Table of Contents
- [useQuery Hook](#usequery-hook)
- [Query Variables](#query-variables)
- [Loading and Error States](#loading-and-error-states)
- [useLazyQuery](#uselazyquery)
- [Polling and Refetching](#polling-and-refetching)
- [Fetch Policies](#fetch-policies)
- [Conditional Queries](#conditional-queries)
## useQuery Hook
The `useQuery` hook is the primary way to fetch data in Apollo Client in non-suspenseful applications. It returns loading and error states that must be handled.
> **Note**: In suspenseful applications, use `useSuspenseQuery` or `useBackgroundQuery` instead. See the [Suspense Hooks reference](suspense-hooks.md) for more details.
### Basic Usage
```tsx
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
const GET_DOGS = gql`
query GetDogs {
dogs {
id
breed
displayImage
}
}
`;
function Dogs() {
const { loading, error, data } = useQuery(GET_DOGS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{data?.dogs.map((dog) => <li key={dog.id}>{dog.breed}</li>)}</ul>;
}
```
### Return Object
```typescript
const {
data, // Query result data
loading, // True during initial load
error, // ApolloError if request failed
networkStatus, // Detailed network state (1-8)
dataState, // For TypeScript type narrowing (AC 4.x)
refetch, // Function to re-execute query
fetchMore, // Function for pagination
startPolling, // Start polling at interval
stopPolling, // Stop polling
subscribeToMore, // Add subscription to query
updateQuery, // Manually update query result
client, // Apollo Client instance
called, // True if query has been executed
previousData, // Previous data (useful during loading)
} = useQuery(QUERY);
```
## Query Variables
### Basic Variables
```tsx
const GET_DOG = gql`
query GetDog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function DogPhoto({ breed }: { breed: string }) {
const { loading, error, data } = useQuery(GET_DOG, {
variables: { breed },
});
if (loading) return null;
if (error) return <p>Error: {error.message}</p>;
return <img src={data.dog.displayImage} alt={breed} />;
}
```
### TypeScript Types
Use `TypedDocumentNode` instead of generic type parameters for better type safety:
```typescript
import { gql, TypedDocumentNode } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
interface GetDogData {
dog: {
id: string;
displayImage: string;
};
}
interface GetDogVariables {
breed: string;
}
const GET_DOG: TypedDocumentNode<GetDogData, GetDogVariables> = gql`
query GetDog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
const { data } = useQuery(GET_DOG, {
variables: { breed: "bulldog" },
});
// data?.dog is fully typed
```
### Dynamic Variables
```tsx
function DogSelector() {
const [breed, setBreed] = useState("bulldog");
// Query automatically re-runs when breed changes
const { data } = useQuery(GET_DOG, {
variables: { breed },
});
return (
<select value={breed} onChange={(e) => setBreed(e.target.value)}>
<option value="bulldog">Bulldog</option>
<option value="poodle">Poodle</option>
</select>
);
}
```
## Loading and Error States
### Using Previous Data
```tsx
function UserProfile({ userId }: { userId: string }) {
const { loading, data, previousData } = useQuery(GET_USER, {
variables: { id: userId },
});
// Show previous data while loading new data
const displayData = data ?? previousData;
return (
<div>
{loading && <LoadingSpinner />}
{displayData && <UserCard user={displayData.user} />}
</div>
);
}
```
### Network Status
```tsx
import { NetworkStatus } from "@apollo/client";
function Dogs() {
const { loading, error, data, networkStatus, refetch } = useQuery(GET_DOGS, {
notifyOnNetworkStatusChange: true,
});
if (networkStatus === NetworkStatus.refetch) {
return <p>Refetching...</p>;
}
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<button onClick={() => refetch()}>Refresh</button>
<ul>
{data.dogs.map((dog) => (
<li key={dog.id}>{dog.breed}</li>
))}
</ul>
</>
);
}
```
## useLazyQuery
Use `useLazyQuery` when you want to execute a query in response to a user-triggered event (like a button click) rather than on component mount.
**Important**: `useLazyQuery` doesn't guarantee a network request - it only sets variables. If data is already in the cache, this isn't a "refetch". Only use `useLazyQuery` if you consume the second tuple value (loading, data, error states) to synchronize cache data with the component. If you only need the promise, use `client.query` directly instead.
### Basic Usage
```tsx
import { gql } from "@apollo/client";
import { useLazyQuery } from "@apollo/client/react";
const GET_DOG_PHOTO = gql`
query GetDogPhoto($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`;
function DelayedQuery() {
const [getDog, { loading, error, data, called }] =
useLazyQuery(GET_DOG_PHOTO);
if (called && loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
{data?.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: "bulldog" } })}>
Get Bulldog Photo
</button>
</div>
);
}
```
### When to Use client.query Instead
If you only need the promise result and don't consume the loading/error/data states from the hook, use `client.query` instead:
```tsx
import { useApolloClient } from "@apollo/client/react";
function SearchDogs() {
const client = useApolloClient();
const [search, setSearch] = useState("");
const handleSearch = async () => {
try {
const { data } = await client.query({
query: SEARCH_DOGS,
variables: { query: search },
});
console.log("Found dogs:", data.searchDogs);
} catch (error) {
console.error("Search failed:", error);
}
};
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)} />
<button onClick={handleSearch}>Search</button>
</div>
);
}
```
## Polling and Refetching
### Polling
```tsx
function LiveFeed() {
const { data, startPolling, stopPolling } = useQuery(GET_FEED, {
pollInterval: 5000, // Poll every 5 seconds
});
// Or control polling dynamically
useEffect(() => {
startPolling(5000);
return () => stopPolling();
}, [startPolling, stopPolling]);
return <Feed items={data?.feed} />;
}
```
### Manual Refetching
```tsx
function DogList() {
const { data, refetch } = useQuery(GET_DOGS);
return (
<div>
<button onClick={() => refetch()}>Refresh</button>
<button onClick={() => refetch({ breed: "poodle" })}>
Refetch Poodles
</button>
<ul>{data?.dogs.map((dog) => <li key={dog.id}>{dog.breed}</li>)}</ul>
</div>
);
}
```
## Fetch Policies
Control how the query interacts with the cache.
| Policy | Description |
| ------------------- | ---------------------------------------------------------- |
| `cache-first` | Return cached data if available, otherwise fetch (default) |
| `cache-only` | Only return cached data, never fetch |
| `cache-and-network` | Return cached data immediately, then fetch and update |
| `network-only` | Always fetch, update cache, ignore cached data |
| `no-cache` | Always fetch, never read or write cache |
| `standby` | Same as cache-first but doesn't auto-update |
### Usage Examples
```tsx
// Real-time data - always fetch
const { data } = useQuery(GET_NOTIFICATIONS, {
fetchPolicy: "network-only",
});
// Static data - prefer cache
const { data } = useQuery(GET_CATEGORIES, {
fetchPolicy: "cache-first",
});
// Show cached data while fetching fresh data
const { data, loading } = useQuery(GET_POSTS, {
fetchPolicy: "cache-and-network",
});
// Fetch once, then use cache
const { data } = useQuery(GET_USER_PROFILE, {
fetchPolicy: "network-only",
nextFetchPolicy: "cache-first",
});
```
### nextFetchPolicy
```tsx
// First request: network-only
// Subsequent requests: cache-first
const { data } = useQuery(GET_POSTS, {
fetchPolicy: "network-only",
nextFetchPolicy: "cache-first",
});
// Or use a function for more control
const { data } = useQuery(GET_POSTS, {
fetchPolicy: "network-only",
nextFetchPolicy: (currentFetchPolicy, { reason, observable }) => {
if (reason === "after-fetch") {
return "cache-first";
}
return currentFetchPolicy;
},
});
```
## Conditional Queries
### Using skipToken (Recommended)
Use `skipToken` to conditionally skip queries without TypeScript issues:
```tsx
import { skipToken } from "@apollo/client";
function UserProfile({ userId }: { userId: string | null }) {
const { data } = useQuery(
GET_USER,
!userId ? skipToken : (
{
variables: { id: userId },
}
)
);
return userId ? <Profile user={data?.user} /> : <p>Select a user</p>;
}
```
### Skip Option (Alternative)
```tsx
function UserProfile({ userId }: { userId: string | null }) {
const { data } = useQuery(GET_USER, {
variables: { id: userId! },
skip: !userId, // Don't execute if no userId
});
return userId ? <Profile user={data?.user} /> : <p>Select a user</p>;
}
```
> **Note**: Using `skipToken` is preferred over `skip` as it avoids TypeScript issues with required variables and the non-null assertion operator.
### SSR Skip
```tsx
// Skip during server-side rendering
const { data } = useQuery(GET_USER_LOCATION, {
skip: typeof window === "undefined",
ssr: false,
});
```