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