or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

components.mdhoc.mdhooks.mdindex.mdssr.mdtesting.md
tile.json

hoc.mddocs/

React Apollo Higher-Order Components

React Apollo HOCs provide a class-based interface for GraphQL operations using higher-order component patterns. These HOCs automatically detect operation types and inject GraphQL data as props.

Capabilities

graphql HOC

Generic higher-order component that automatically detects GraphQL operation types and provides appropriate functionality.

/**
 * Higher-order component for GraphQL operations
 * @param document - GraphQL document (query, mutation, or subscription)
 * @param operationOptions - Configuration options for the operation
 * @returns HOC function that wraps components with GraphQL functionality
 */
function graphql<TProps = {}, TData = {}, TGraphQLVariables = {}, TChildProps = {}>(
  document: DocumentNode,
  operationOptions?: OperationOption<TProps, TData, TGraphQLVariables, TChildProps>
): (WrappedComponent: React.ComponentType<TChildProps>) => React.ComponentType<TProps>;

interface OperationOption<TProps, TData, TGraphQLVariables, TChildProps> {
  options?: (props: TProps) => QueryFunctionOptions<TData, TGraphQLVariables> | 
            BaseMutationOptions<TData, TGraphQLVariables> | 
            BaseSubscriptionOptions<TData, TGraphQLVariables>;
  props?: (
    options: {
      data?: QueryResult<TData, TGraphQLVariables>;
      mutate?: MutationFunction<TData, TGraphQLVariables>;
      result?: MutationResult<TData>;
    },
    ownProps: TProps
  ) => TChildProps;
  skip?: boolean | ((props: TProps) => boolean);
  name?: string;
  withRef?: boolean;
  alias?: string;
}

Usage Examples:

import React from "react";
import { graphql } from "react-apollo";
import gql from "graphql-tag";

// Query example
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

interface UserProps {
  userId: string;
}

interface UserData {
  user: {
    id: string;
    name: string;
    email: string;
  };
}

const UserComponent = ({ data }) => {
  if (data.loading) return <div>Loading...</div>;
  if (data.error) return <div>Error: {data.error.message}</div>;
  
  return (
    <div>
      <h2>{data.user.name}</h2>
      <p>{data.user.email}</p>
    </div>
  );
};

const UserWithData = graphql<UserProps, UserData>(GET_USER, {
  options: (props) => ({
    variables: { id: props.userId }
  })
})(UserComponent);

// Mutation example
const CREATE_USER = gql`
  mutation CreateUser($input: UserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

const CreateUserForm = ({ mutate }) => {
  const handleSubmit = async (formData) => {
    try {
      const result = await mutate({
        variables: { input: formData }
      });
      console.log('User created:', result.data.createUser);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit">Create User</button>
    </form>
  );
};

const CreateUserWithMutation = graphql(CREATE_USER)(CreateUserForm);

withQuery HOC

Specialized higher-order component specifically for GraphQL queries.

/**
 * Higher-order component specifically for GraphQL queries
 * @param document - GraphQL query document
 * @param operationOptions - Query-specific configuration options
 * @returns HOC function that wraps components with query data
 */
function withQuery<TProps = {}, TData = {}, TGraphQLVariables = {}>(
  document: DocumentNode,
  operationOptions?: OperationOption<TProps, TData, TGraphQLVariables>
): (WrappedComponent: React.ComponentType) => React.ComponentType<TProps>;

Usage Examples:

import React from "react";
import { withQuery } from "react-apollo";
import gql from "graphql-tag";

const GET_POSTS = gql`
  query GetPosts($limit: Int, $offset: Int) {
    posts(limit: $limit, offset: $offset) {
      id
      title
      content
      author {
        name
      }
    }
  }
`;

const PostList = ({ data, loadMore }) => {
  if (data.loading && !data.posts) return <div>Loading posts...</div>;
  if (data.error) return <div>Error: {data.error.message}</div>;

  return (
    <div>
      {data.posts.map((post) => (
        <article key={post.id}>
          <h3>{post.title}</h3>
          <p>By {post.author.name}</p>
          <p>{post.content}</p>
        </article>
      ))}
      <button onClick={loadMore} disabled={data.loading}>
        {data.loading ? 'Loading...' : 'Load More'}
      </button>
    </div>
  );
};

const PostListWithQuery = withQuery(GET_POSTS, {
  options: (props) => ({
    variables: { limit: 10, offset: 0 },
    fetchPolicy: 'cache-and-network'
  }),
  props: ({ data, ownProps }) => ({
    data,
    loadMore: () => {
      return data.fetchMore({
        variables: {
          offset: data.posts.length
        },
        updateQuery: (prev, { fetchMoreResult }) => {
          if (!fetchMoreResult) return prev;
          return {
            posts: [...prev.posts, ...fetchMoreResult.posts]
          };
        }
      });
    }
  })
})(PostList);

withMutation HOC

Specialized higher-order component specifically for GraphQL mutations.

/**
 * Higher-order component specifically for GraphQL mutations
 * @param document - GraphQL mutation document
 * @param operationOptions - Mutation-specific configuration options
 * @returns HOC function that wraps components with mutation function
 */
function withMutation<TProps = {}, TData = {}, TGraphQLVariables = {}>(
  document: DocumentNode,
  operationOptions?: OperationOption<TProps, TData, TGraphQLVariables>
): (WrappedComponent: React.ComponentType) => React.ComponentType<TProps>;

Usage Examples:

import React from "react";
import { withMutation } from "react-apollo";
import gql from "graphql-tag";

const UPDATE_USER = gql`
  mutation UpdateUser($id: ID!, $input: UserUpdateInput!) {
    updateUser(id: $id, input: $input) {
      id
      name
      email
    }
  }
`;

class UserProfile extends React.Component {
  state = {
    editing: false,
    name: this.props.user.name,
    email: this.props.user.email
  };

  handleSave = async () => {
    const { mutate, user } = this.props;
    const { name, email } = this.state;

    try {
      await mutate({
        variables: {
          id: user.id,
          input: { name, email }
        },
        optimisticResponse: {
          updateUser: {
            __typename: 'User',
            id: user.id,
            name,
            email
          }
        }
      });
      this.setState({ editing: false });
    } catch (error) {
      console.error('Update failed:', error);
    }
  };

  render() {
    const { editing, name, email } = this.state;

    if (editing) {
      return (
        <form onSubmit={this.handleSave}>
          <input
            value={name}
            onChange={(e) => this.setState({ name: e.target.value })}
          />
          <input
            value={email}
            onChange={(e) => this.setState({ email: e.target.value })}
          />
          <button type="submit">Save</button>
          <button onClick={() => this.setState({ editing: false })}>
            Cancel
          </button>
        </form>
      );
    }

    return (
      <div>
        <h2>{name}</h2>
        <p>{email}</p>
        <button onClick={() => this.setState({ editing: true })}>
          Edit
        </button>
      </div>
    );
  }
}

const UserProfileWithMutation = withMutation(UPDATE_USER)(UserProfile);

withSubscription HOC

Specialized higher-order component specifically for GraphQL subscriptions.

/**
 * Higher-order component specifically for GraphQL subscriptions
 * @param document - GraphQL subscription document
 * @param operationOptions - Subscription-specific configuration options
 * @returns HOC function that wraps components with subscription data
 */
function withSubscription<TProps = {}, TData = {}, TGraphQLVariables = {}>(
  document: DocumentNode,
  operationOptions?: OperationOption<TProps, TData, TGraphQLVariables>
): (WrappedComponent: React.ComponentType) => React.ComponentType<TProps>;

Usage Examples:

import React from "react";
import { withSubscription } from "react-apollo";
import gql from "graphql-tag";

const COMMENT_SUBSCRIPTION = gql`
  subscription OnCommentAdded($postId: ID!) {
    commentAdded(postId: $postId) {
      id
      content
      author {
        name
      }
      createdAt
    }
  }
`;

const LiveComments = ({ data, postId }) => {
  if (data.loading) return <div>Connecting...</div>;
  if (data.error) return <div>Connection error: {data.error.message}</div>;

  if (data.commentAdded) {
    return (
      <div className="live-comment">
        <strong>{data.commentAdded.author.name}</strong>
        <p>{data.commentAdded.content}</p>
        <small>{new Date(data.commentAdded.createdAt).toLocaleTimeString()}</small>
      </div>
    );
  }

  return <div>Waiting for new comments...</div>;
};

const LiveCommentsWithSubscription = withSubscription(COMMENT_SUBSCRIPTION, {
  options: (props) => ({
    variables: { postId: props.postId }
  })
})(LiveComments);

withApollo HOC

Higher-order component that provides direct access to the Apollo Client instance.

/**
 * Higher-order component that injects Apollo Client
 * @param WrappedComponent - Component to wrap with Apollo Client
 * @returns HOC that provides client prop
 */
function withApollo<TProps>(
  WrappedComponent: React.ComponentType<WithApolloClient<TProps>>
): React.ComponentType<TProps>;

type WithApolloClient<P> = P & { client: ApolloClient<any> };

Usage Examples:

import React from "react";
import { withApollo } from "react-apollo";

class DataManager extends React.Component {
  handleClearCache = () => {
    this.props.client.clearStore();
  };

  handleDirectQuery = async () => {
    const { client } = this.props;
    
    try {
      const result = await client.query({
        query: GET_USER_QUERY,
        variables: { id: '123' },
        fetchPolicy: 'network-only'
      });
      console.log('Query result:', result.data);
    } catch (error) {
      console.error('Query failed:', error);
    }
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClearCache}>
          Clear Cache
        </button>
        <button onClick={this.handleDirectQuery}>
          Fetch User Directly
        </button>
      </div>
    );
  }
}

const DataManagerWithApollo = withApollo(DataManager);

HOC Types

HOC-Specific Types

interface QueryControls<TData = any, TGraphQLVariables = OperationVariables> {
  error?: ApolloError;
  networkStatus: number;
  loading: boolean;
  variables: TGraphQLVariables;
  fetchMore: (fetchMoreOptions: FetchMoreOptions & FetchMoreQueryOptions) => Promise<ApolloQueryResult<TData>>;
  refetch: (variables?: TGraphQLVariables) => Promise<ApolloQueryResult<TData>>;
  startPolling: (pollInterval: number) => void;
  stopPolling: () => void;
  subscribeToMore: (options: SubscribeToMoreOptions) => () => void;
  updateQuery: (mapFn: (previousQueryResult: TData, options: UpdateQueryOptions) => TData) => void;
}

type DataValue<TData, TGraphQLVariables = OperationVariables> = 
  QueryControls<TData, TGraphQLVariables> & Partial<TData>;

interface DataProps<TData, TGraphQLVariables = OperationVariables> {
  data: DataValue<TData, TGraphQLVariables>;
}

interface MutateProps<TData = any, TGraphQLVariables = OperationVariables> {
  mutate: MutationFunction<TData, TGraphQLVariables>;
  result: MutationResult<TData>;
}

type ChildProps<TProps = {}, TData = {>, TGraphQLVariables = OperationVariables> = 
  TProps & Partial<DataProps<TData, TGraphQLVariables>> & Partial<MutateProps<TData, TGraphQLVariables>>;

type ChildDataProps<TProps = {}, TData = {}, TGraphQLVariables = OperationVariables> = 
  TProps & DataProps<TData, TGraphQLVariables>;

type ChildMutateProps<TProps = {}, TData = {}, TGraphQLVariables = OperationVariables> = 
  TProps & MutateProps<TData, TGraphQLVariables>;

interface OptionProps<TProps = any, TData = any, TGraphQLVariables = OperationVariables> {
  ownProps: TProps;
  data?: QueryResult<TData, TGraphQLVariables>;
  mutate?: MutationFunction<TData, TGraphQLVariables>;
}