UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

417 lines (327 loc) 10.3 kB
# 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, }); ```