or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

caching.mdcore-client.mderror-handling.mdindex.mdlink-system.mdmasking.mdreact-hooks.mdtesting.mdutilities.md
tile.json

react-hooks.mddocs/

React Integration

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.

Capabilities

Apollo Provider

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>
  );
}

Query Hook

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>
  );
}

Lazy Query Hook

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>
  );
}

Mutation Hook

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>
  );
}

Subscription Hook

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>
  );
}

Fragment Hook

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>
  );
}

Background Query Hook

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 Query Hook

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>;
}

Loadable Query Hook

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>
  );
}

Suspense Fragment Hook

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>
  );
}

Suspense Query Hook

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>
  );
}

Utility Hooks

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
}

Types

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;