React hooks for GraphQL operations with Suspense support and comprehensive state management. Apollo Client provides declarative data fetching for React applications through a comprehensive set of hooks that handle loading states, error handling, and reactive updates automatically.
React context provider that makes Apollo Client available throughout the component tree.
/**
* React context provider for Apollo Client
* Makes client instance available to all child components
*/
interface ApolloProvider {
/** Apollo Client instance */
client: ApolloClient<any>;
/** Child components */
children: ReactNode;
}
/**
* Get Apollo context for advanced use cases
* @returns Apollo context object
*/
function getApolloContext(): Context<ApolloContextValue>;
interface ApolloContextValue {
client?: ApolloClient<any>;
renderPromises?: RenderPromises;
}Usage Example:
import { ApolloProvider } from "@apollo/client/react";
function App() {
return (
<ApolloProvider client={client}>
<UsersList />
<PostsList />
</ApolloProvider>
);
}Execute GraphQL queries with automatic loading states and reactive updates.
/**
* Execute a GraphQL query with automatic loading states and caching
* @param query - GraphQL query document
* @param options - Query configuration options
* @returns Query result with data, loading state, and utilities
*/
function useQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: QueryHookOptions<TData, TVariables>
): QueryResult<TData, TVariables>;
interface QueryHookOptions<TData, TVariables> {
/** Query variables */
variables?: TVariables;
/** Skip query execution */
skip?: boolean;
/** Polling interval in milliseconds */
pollInterval?: number;
/** Notify on network status changes */
notifyOnNetworkStatusChange?: boolean;
/** Fetch policy for cache interaction */
fetchPolicy?: WatchQueryFetchPolicy;
/** Error policy */
errorPolicy?: ErrorPolicy;
/** Return partial data from cache */
returnPartialData?: boolean;
/** Context passed to links */
context?: DefaultContext;
/** Callback when query completes */
onCompleted?: (data: TData) => void;
/** Callback when query errors */
onError?: (error: ApolloError) => void;
}
interface QueryResult<TData, TVariables> {
/** Query result data */
data: TData | undefined;
/** Loading state */
loading: boolean;
/** GraphQL or network errors */
error?: ApolloError;
/** Current network status */
networkStatus: NetworkStatus;
/** Refetch the query */
refetch: (variables?: Partial<TVariables>) => Promise<ApolloQueryResult<TData>>;
/** Fetch more data for pagination */
fetchMore: <TFetchData = TData, TFetchVars = TVariables>(
options: FetchMoreQueryOptions<TFetchVars, TFetchData>
) => Promise<ApolloQueryResult<TData>>;
/** Subscribe to more data (subscriptions) */
subscribeToMore: <TSubscriptionData = TData>(
options: SubscribeToMoreOptions<TData, TVariables, TSubscriptionData>
) => () => void;
/** Update query data optimistically */
updateQuery: (
mapFn: (previousQueryResult: TData, options: UpdateQueryOptions<TVariables>) => TData
) => void;
/** Start polling */
startPolling: (pollInterval: number) => void;
/** Stop polling */
stopPolling: () => void;
/** Apollo Client instance */
client: ApolloClient<any>;
/** Called indicator */
called: boolean;
/** Previous data (useful during loading) */
previousData?: TData;
}Usage Example:
import { useQuery, gql } from "@apollo/client/react";
const GET_USERS = gql`
query GetUsers($limit: Int) {
users(limit: $limit) {
id
name
email
avatar
}
}
`;
function UsersList() {
const { loading, error, data, refetch, fetchMore } = useQuery(GET_USERS, {
variables: { limit: 10 },
pollInterval: 30000, // Poll every 30 seconds
onCompleted: (data) => {
console.log(`Loaded ${data.users.length} users`);
}
});
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
{data.users.map(user => (
<UserCard key={user.id} user={user} />
))}
<button onClick={() => refetch()}>
Refresh
</button>
<button onClick={() =>
fetchMore({
variables: { offset: data.users.length, limit: 10 },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
...fetchMoreResult,
users: [...prev.users, ...fetchMoreResult.users]
};
}
})
}>
Load More
</button>
</div>
);
}Execute queries programmatically rather than on component mount.
/**
* Create a lazy query function for programmatic execution
* @param query - GraphQL query document
* @param options - Query configuration options
* @returns Tuple with execute function and query result
*/
function useLazyQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: LazyQueryHookOptions<TData, TVariables>
): LazyQueryResultTuple<TData, TVariables>;
type LazyQueryResultTuple<TData, TVariables> = [
LazyQueryExecFunction<TData, TVariables>,
QueryResult<TData, TVariables>
];
type LazyQueryExecFunction<TData, TVariables> = (
options?: LazyQueryHookExecOptions<TData, TVariables>
) => Promise<QueryResult<TData, TVariables>>;
interface LazyQueryHookOptions<TData, TVariables>
extends Omit<QueryHookOptions<TData, TVariables>, 'skip'> {}
interface LazyQueryHookExecOptions<TData, TVariables> {
variables?: TVariables;
context?: DefaultContext;
}Usage Example:
function SearchUsers() {
const [searchUsers, { loading, error, data, called }] = useLazyQuery(
gql`
query SearchUsers($query: String!) {
searchUsers(query: $query) {
id
name
email
}
}
`
);
const handleSearch = (searchTerm: string) => {
if (searchTerm.length > 2) {
searchUsers({
variables: { query: searchTerm }
});
}
};
return (
<div>
<SearchInput onSearch={handleSearch} />
{called && loading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && (
<SearchResults users={data.searchUsers} />
)}
</div>
);
}Execute GraphQL mutations with optimistic updates and cache management.
/**
* Execute GraphQL mutations with cache updates and optimistic responses
* @param mutation - GraphQL mutation document
* @param options - Mutation configuration options
* @returns Tuple with mutate function and mutation result
*/
function useMutation<TData = any, TVariables = OperationVariables>(
mutation: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: MutationHookOptions<TData, TVariables>
): MutationTuple<TData, TVariables>;
type MutationTuple<TData, TVariables> = [
MutationFunction<TData, TVariables>,
MutationResult<TData>
];
type MutationFunction<TData, TVariables> = (
options?: MutationFunctionOptions<TData, TVariables>
) => Promise<FetchResult<TData>>;
interface MutationHookOptions<TData, TVariables> {
/** Default variables */
variables?: TVariables;
/** Optimistic response for immediate UI updates */
optimisticResponse?: TData | ((vars: TVariables) => TData);
/** Function to update cache after mutation */
update?: MutationUpdaterFunction<TData, TVariables>;
/** Queries to refetch after mutation */
refetchQueries?: RefetchQueriesInclude;
/** How to handle the mutation result in cache */
fetchPolicy?: MutationFetchPolicy;
/** Error policy */
errorPolicy?: ErrorPolicy;
/** Context passed to links */
context?: DefaultContext;
/** Callback when mutation completes */
onCompleted?: (data: TData) => void;
/** Callback when mutation errors */
onError?: (error: ApolloError) => void;
}
interface MutationResult<TData> {
/** Mutation result data */
data?: TData;
/** Loading state */
loading: boolean;
/** GraphQL or network errors */
error?: ApolloError;
/** Whether mutation has been called */
called: boolean;
/** Apollo Client instance */
client: ApolloClient<any>;
/** Reset mutation state */
reset: () => void;
}Usage Example:
import { useMutation, gql } from "@apollo/client/react";
const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
function CreateUserForm() {
const [createUser, { loading, error, data }] = useMutation(CREATE_USER, {
update: (cache, { data }) => {
if (data?.createUser) {
// Update the users list in cache
cache.modify({
fields: {
users(existingUsers = []) {
const newUserRef = cache.writeFragment({
data: data.createUser,
fragment: gql`
fragment NewUser on User {
id
name
email
}
`
});
return [...existingUsers, newUserRef];
}
}
});
}
},
onCompleted: (data) => {
console.log('User created:', data.createUser);
},
onError: (error) => {
console.error('Failed to create user:', error);
}
});
const handleSubmit = (formData) => {
createUser({
variables: {
input: {
name: formData.name,
email: formData.email
}
},
optimisticResponse: {
createUser: {
id: 'temp-id',
name: formData.name,
email: formData.email,
__typename: 'User'
}
}
});
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create User'}
</button>
{error && <ErrorMessage error={error} />}
{data && <SuccessMessage user={data.createUser} />}
</form>
);
}Execute GraphQL subscriptions for real-time data updates.
/**
* Execute GraphQL subscriptions for real-time updates
* @param subscription - GraphQL subscription document
* @param options - Subscription configuration options
* @returns Subscription result with data and state
*/
function useSubscription<TData = any, TVariables = OperationVariables>(
subscription: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: SubscriptionHookOptions<TData, TVariables>
): SubscriptionResult<TData>;
interface SubscriptionHookOptions<TData, TVariables> {
/** Subscription variables */
variables?: TVariables;
/** Skip subscription execution */
skip?: boolean;
/** Fetch policy */
fetchPolicy?: FetchPolicy;
/** Error policy */
errorPolicy?: ErrorPolicy;
/** Context passed to links */
context?: DefaultContext;
/** Callback when subscription receives data */
onData?: (options: OnDataOptions<TData>) => void;
/** Callback when subscription completes */
onComplete?: () => void;
/** Callback when subscription errors */
onError?: (error: ApolloError) => void;
}
interface SubscriptionResult<TData> {
/** Subscription data */
data?: TData;
/** Loading state */
loading: boolean;
/** GraphQL or network errors */
error?: ApolloError;
/** Apollo Client instance */
client: ApolloClient<any>;
}Usage Example:
import { useSubscription, gql } from "@apollo/client/react";
const MESSAGE_SUBSCRIPTION = gql`
subscription MessageAdded($channel: String!) {
messageAdded(channel: $channel) {
id
content
user {
name
avatar
}
createdAt
}
}
`;
function ChatMessages({ channel }) {
const { data, loading, error } = useSubscription(MESSAGE_SUBSCRIPTION, {
variables: { channel },
onData: ({ data }) => {
if (data?.messageAdded) {
// Optionally play notification sound
playNotificationSound();
// Update cache
client.cache.modify({
fields: {
messages(existingMessages = []) {
const newMessageRef = client.cache.writeFragment({
data: data.messageAdded,
fragment: gql`
fragment NewMessage on Message {
id
content
user { name avatar }
createdAt
}
`
});
return [...existingMessages, newMessageRef];
}
}
});
}
},
onError: (error) => {
console.error('Subscription error:', error);
}
});
if (loading) return <div>Connecting to chat...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div className="chat-messages">
{data?.messageAdded && (
<div className="new-message">
<strong>{data.messageAdded.user.name}:</strong>
{data.messageAdded.content}
</div>
)}
</div>
);
}Read fragment data from the cache with reactive updates.
/**
* Read fragment data from cache with reactive updates
* @param options - Fragment read options
* @returns Fragment result with data and metadata
*/
function useFragment<TData, TVars>(
options: UseFragmentOptions<TData, TVars>
): UseFragmentResult<TData>;
interface UseFragmentOptions<TData, TVars> {
/** Fragment document */
fragment: DocumentNode | TypedDocumentNode<TData, TVars>;
/** Fragment variables */
variables?: TVars;
/** Cache ID or object with __typename and key fields */
from: StoreObject | Reference | string;
/** Return partial data if some fields missing */
returnPartialData?: boolean;
/** Optimistic read */
optimistic?: boolean;
}
interface UseFragmentResult<TData> {
/** Fragment data */
data: TData | undefined;
/** Whether all requested data is available */
complete: boolean;
/** Missing fields information */
missing?: MissingTree;
}Usage Example:
import { useFragment, gql } from "@apollo/client/react";
const USER_FRAGMENT = gql`
fragment UserInfo on User {
id
name
email
avatar
posts {
id
title
}
}
`;
function UserProfile({ userId }) {
const { data: user, complete } = useFragment({
fragment: USER_FRAGMENT,
from: {
__typename: 'User',
id: userId
}
});
if (!user) return <div>User not found</div>;
if (!complete) return <div>Loading user data...</div>;
return (
<div className="user-profile">
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
<h3>Posts ({user.posts.length})</h3>
{user.posts.map(post => (
<PostSummary key={post.id} post={post} />
))}
</div>
);
}Execute queries in the background and return a query reference for later use.
/**
* Execute query in background and return query reference
* @param query - GraphQL query document
* @param options - Background query options
* @returns Tuple with query reference and utilities
*/
function useBackgroundQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: BackgroundQueryHookOptions<TData, TVariables>
): UseBackgroundQueryResult<TData, TVariables>;
interface BackgroundQueryHookOptions<TData, TVariables> {
/** Query variables */
variables?: TVariables;
/** Context passed to links */
context?: DefaultContext;
/** Error policy */
errorPolicy?: ErrorPolicy;
/** Return partial data from cache */
returnPartialData?: boolean;
}
type UseBackgroundQueryResult<TData, TVariables> = [
QueryRef<TData, TVariables>,
{
refetch: (variables?: Partial<TVariables>) => void;
fetchMore: <TFetchData = TData, TFetchVars = TVariables>(
options: FetchMoreQueryOptions<TFetchVars, TFetchData>
) => Promise<ApolloQueryResult<TData>>;
}
];Usage Example:
function BlogPost({ postId }) {
const [queryRef, { refetch }] = useBackgroundQuery(GET_POST, {
variables: { id: postId }
});
return (
<Suspense fallback={<PostSkeleton />}>
<PostContent queryRef={queryRef} onRefresh={() => refetch()} />
</Suspense>
);
}
function PostContent({ queryRef, onRefresh }) {
const { data } = useReadQuery(queryRef);
return (
<article>
<h1>{data.post.title}</h1>
<p>{data.post.content}</p>
<button onClick={onRefresh}>Refresh</button>
</article>
);
}Read data from a query reference created by useBackgroundQuery.
/**
* Read data from query reference
* @param queryRef - Query reference from useBackgroundQuery
* @returns Query result data
*/
function useReadQuery<TData = any, TVariables = OperationVariables>(
queryRef: QueryRef<TData, TVariables>
): UseReadQueryResult<TData, TVariables>;
interface UseReadQueryResult<TData, TVariables> {
/** Query result data (always available) */
data: TData;
/** GraphQL or network errors */
error?: ApolloError;
/** Current network status */
networkStatus: NetworkStatus;
/** Apollo Client instance */
client: ApolloClient<any>;
}Query with manual loading control for user-initiated data fetching.
/**
* Create a manually loadable query
* @param query - GraphQL query document
* @param options - Loadable query options
* @returns Tuple with load function, result, and query reference
*/
function useLoadableQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: LoadableQueryHookOptions<TData, TVariables>
): UseLoadableQueryResult<TData, TVariables>;
interface LoadableQueryHookOptions<TData, TVariables> {
/** Error policy */
errorPolicy?: ErrorPolicy;
/** Context passed to links */
context?: DefaultContext;
/** Return partial data from cache */
returnPartialData?: boolean;
}
type UseLoadableQueryResult<TData, TVariables> = [
LoadQueryFunction<TData, TVariables>,
QueryResult<TData, TVariables>,
QueryRef<TData, TVariables> | undefined
];
type LoadQueryFunction<TData, TVariables> = (
variables?: TVariables
) => void;Usage Example:
function SearchResults() {
const [loadQuery, { loading, error, data }, queryRef] = useLoadableQuery(
SEARCH_QUERY
);
const handleSearch = (searchTerm: string) => {
if (searchTerm) {
loadQuery({ query: searchTerm });
}
};
return (
<div>
<SearchInput onSearch={handleSearch} />
{loading && <SearchSkeleton />}
{error && <ErrorDisplay error={error} />}
{data && <SearchResultsList results={data.search} />}
{queryRef && (
<Suspense fallback={<LoadingMore />}>
<SearchResultDetails queryRef={queryRef} />
</Suspense>
)}
</div>
);
}Read fragment data with React Suspense support.
/**
* Read fragment data with Suspense support
* @param options - Suspense fragment options
* @returns Fragment result with data
*/
function useSuspenseFragment<TData, TVars>(
options: UseSuspenseFragmentOptions<TData, TVars>
): UseSuspenseFragmentResult<TData>;
interface UseSuspenseFragmentOptions<TData, TVars> {
/** Fragment document */
fragment: DocumentNode | TypedDocumentNode<TData, TVars>;
/** Fragment variables */
variables?: TVars;
/** Cache ID or object with __typename and key fields */
from: StoreObject | Reference | string;
/** Optimistic read */
optimistic?: boolean;
}
interface UseSuspenseFragmentResult<TData> {
/** Fragment data (always available) */
data: TData;
}Usage Example:
const USER_FRAGMENT = gql`
fragment UserProfile on User {
id
name
email
avatar
bio
}
`;
function UserProfileCard({ userId }) {
const { data: user } = useSuspenseFragment({
fragment: USER_FRAGMENT,
from: {
__typename: 'User',
id: userId
}
});
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
<p>{user.bio}</p>
</div>
);
}
function UserList({ userIds }) {
return (
<div>
{userIds.map(id => (
<Suspense key={id} fallback={<UserCardSkeleton />}>
<UserProfileCard userId={id} />
</Suspense>
))}
</div>
);
}Query with React Suspense support for concurrent rendering.
/**
* Execute query with React Suspense support
* @param query - GraphQL query document
* @param options - Suspense query options
* @returns Query result (throws promises during loading)
*/
function useSuspenseQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: SuspenseQueryHookOptions<TData, TVariables>
): UseSuspenseQueryResult<TData, TVariables>;
interface SuspenseQueryHookOptions<TData, TVariables> {
/** Query variables */
variables?: TVariables;
/** Context passed to links */
context?: DefaultContext;
/** Error policy */
errorPolicy?: ErrorPolicy;
/** Return partial data from cache */
returnPartialData?: boolean;
}
interface UseSuspenseQueryResult<TData, TVariables> {
/** Query result data (always available) */
data: TData;
/** GraphQL or network errors */
error?: ApolloError;
/** Current network status */
networkStatus: NetworkStatus;
/** Refetch the query */
refetch: (variables?: Partial<TVariables>) => Promise<ApolloQueryResult<TData>>;
/** Fetch more data for pagination */
fetchMore: <TFetchData = TData, TFetchVars = TVariables>(
options: FetchMoreQueryOptions<TFetchVars, TFetchData>
) => Promise<ApolloQueryResult<TData>>;
/** Subscribe to more data */
subscribeToMore: <TSubscriptionData = TData>(
options: SubscribeToMoreOptions<TData, TVariables, TSubscriptionData>
) => () => void;
/** Apollo Client instance */
client: ApolloClient<any>;
}Usage Example:
import { Suspense } from 'react';
import { useSuspenseQuery, gql } from "@apollo/client/react";
const GET_USER_PROFILE = gql`
query GetUserProfile($id: ID!) {
user(id: $id) {
id
name
email
bio
followersCount
followingCount
}
}
`;
function UserProfile({ userId }) {
// No loading state needed - Suspense handles it
const { data, refetch } = useSuspenseQuery(GET_USER_PROFILE, {
variables: { id: userId }
});
return (
<div>
<h1>{data.user.name}</h1>
<p>{data.user.bio}</p>
<div>
<span>{data.user.followersCount} followers</span>
<span>{data.user.followingCount} following</span>
</div>
<button onClick={() => refetch()}>Refresh</button>
</div>
);
}
function App() {
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile userId="123" />
</Suspense>
);
}Additional hooks for client access and reactive variables.
/**
* Get Apollo Client instance from context
* @returns Apollo Client instance
*/
function useApolloClient(): ApolloClient<object>;
/**
* Subscribe to reactive variable changes
* @param rv - Reactive variable to subscribe to
* @returns Current value of reactive variable
*/
function useReactiveVar<T>(rv: ReactiveVar<T>): T;
/**
* Skip token for conditional queries
*/
const skipToken: SkipToken;
type SkipToken = typeof skipToken;Usage Example:
import { useApolloClient, useReactiveVar } from "@apollo/client/react";
// Reactive variable
const themeVar = makeVar('light');
function ThemeToggle() {
const client = useApolloClient();
const currentTheme = useReactiveVar(themeVar);
const toggleTheme = () => {
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
themeVar(newTheme);
// Optionally clear cache on theme change
client.resetStore();
};
return (
<button onClick={toggleTheme}>
Current theme: {currentTheme}
</button>
);
}
// Conditional query with skipToken
function ConditionalQuery({ shouldFetch, userId }) {
const { data, loading } = useQuery(GET_USER, {
variables: shouldFetch ? { id: userId } : skipToken
});
// Query only executes when shouldFetch is true
}interface FetchMoreQueryOptions<TFetchVars, TFetchData> {
variables?: TFetchVars;
updateQuery?: (
previousQueryResult: TFetchData,
options: {
fetchMoreResult: TFetchData;
variables?: TFetchVars;
}
) => TFetchData;
}
interface SubscribeToMoreOptions<TData, TVariables, TSubscriptionData> {
document: DocumentNode;
variables?: TVariables;
updateQuery?: (
prev: TData,
options: {
subscriptionData: { data: TSubscriptionData; };
variables?: TVariables;
}
) => TData;
onError?: (err: Error) => void;
}
interface UpdateQueryOptions<TVariables> {
variables?: TVariables;
}
interface OnDataOptions<TData> {
client: ApolloClient<object>;
data: SubscriptionResult<TData>;
}
type MutationUpdaterFunction<TData, TVariables> = (
cache: ApolloCache<any>,
result: FetchResult<TData>,
options: {
context?: DefaultContext;
variables?: TVariables;
}
) => void;