You are an expert in Apollo Client, GraphQL, TypeScript, and React development. Apollo Client provides a comprehensive state management solution for GraphQL applications with intelligent caching, optimistic UI updates, and seamless React integration.
src/
components/
graphql/
queries/
users.ts
posts.ts
mutations/
users.ts
posts.ts
fragments/
user.ts
post.ts
hooks/
useUser.ts
usePosts.ts
pages/
utils/
apollo-client.ts
types/
generated/ # Generated TypeScript types
// utils/apollo-client.ts
import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
const httpLink = new HttpLink({
uri: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT,
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => {
console.error(`[GraphQL error]: Message: ${message}, Path: ${path}`);
});
}
if (networkError) {
console.error(`[Network error]: ${networkError}`);
}
});
export const apolloClient = new ApolloClient({
link: from([errorLink, httpLink]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
}),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
errorPolicy: 'all',
},
query: {
fetchPolicy: 'cache-first',
errorPolicy: 'all',
},
mutate: {
errorPolicy: 'all',
},
},
});
// pages/_app.tsx or app/providers.tsx
import { ApolloProvider } from '@apollo/client';
import { apolloClient } from '@/utils/apollo-client';
function App({ children }: { children: React.ReactNode }) {
return (
<ApolloProvider client={apolloClient}>
{children}
</ApolloProvider>
);
}
Use descriptive naming for types, fields, and arguments:
# Good
type User {
id: ID!
firstName: String!
lastName: String!
emailAddress: String!
createdAt: DateTime!
}
type Query {
getUserById(id: ID!): User
getUsersByRole(role: UserRole!): [User!]!
}
# Avoid
type Query {
getUser(id: ID!): User # Less descriptive
}
Define a clear schema reflecting your business domain:
type Query {
user(id: ID!): User
users(first: Int, after: String, filter: UserFilter): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
deleteUser(id: ID!): DeleteUserPayload!
}
input CreateUserInput {
firstName: String!
lastName: String!
email: String!
}
type CreateUserPayload {
user: User
errors: [UserError!]
}
// graphql/fragments/user.ts
import { gql } from '@apollo/client';
export const USER_FIELDS = gql`
fragment UserFields on User {
id
firstName
lastName
email
avatar
createdAt
}
`;
// graphql/queries/users.ts
import { gql } from '@apollo/client';
import { USER_FIELDS } from '../fragments/user';
export const GET_USER = gql`
${USER_FIELDS}
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
}
`;
export const GET_USERS = gql`
${USER_FIELDS}
query GetUsers($first: Int, $after: String) {
users(first: $first, after: $after) {
edges {
node {
...UserFields
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
// hooks/useUser.ts
import { useQuery, QueryHookOptions } from '@apollo/client';
import { GET_USER } from '@/graphql/queries/users';
import { User, GetUserQuery, GetUserQueryVariables } from '@/types/generated';
export function useUser(
id: string,
options?: QueryHookOptions<GetUserQuery, GetUserQueryVariables>
) {
const { data, loading, error, refetch } = useQuery<
GetUserQuery,
GetUserQueryVariables
>(GET_USER, {
variables: { id },
skip: !id,
...options,
});
return {
user: data?.user,
loading,
error,
refetch,
};
}
// graphql/mutations/users.ts
import { gql } from '@apollo/client';
import { USER_FIELDS } from '../fragments/user';
export const CREATE_USER = gql`
${USER_FIELDS}
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
user {
...UserFields
}
errors {
field
message
}
}
}
`;
export const UPDATE_USER = gql`
${USER_FIELDS}
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
user {
...UserFields
}
errors {
field
message
}
}
}
`;
// hooks/useCreateUser.ts
import { useMutation, MutationHookOptions } from '@apollo/client';
import { CREATE_USER } from '@/graphql/mutations/users';
import { GET_USERS } from '@/graphql/queries/users';
export function useCreateUser(options?: MutationHookOptions) {
const [createUser, { data, loading, error }] = useMutation(CREATE_USER, {
refetchQueries: [{ query: GET_USERS }],
onError: (error) => {
console.error('Failed to create user:', error);
},
...options,
});
return {
createUser: (input: CreateUserInput) => createUser({ variables: { input } }),
data,
loading,
error,
};
}
function useUpdateUser() {
const [updateUser] = useMutation(UPDATE_USER, {
optimisticResponse: ({ id, input }) => ({
__typename: 'Mutation',
updateUser: {
__typename: 'UpdateUserPayload',
user: {
__typename: 'User',
id,
...input,
},
errors: null,
},
}),
update: (cache, { data }) => {
const updatedUser = data?.updateUser?.user;
if (updatedUser) {
cache.modify({
id: cache.identify(updatedUser),
fields: {
firstName: () => updatedUser.firstName,
lastName: () => updatedUser.lastName,
},
});
}
},
});
return { updateUser };
}
const cache = new InMemoryCache({
typePolicies: {
User: {
keyFields: ['id'],
},
Post: {
keyFields: ['id'],
fields: {
author: {
merge: true,
},
},
},
},
});
// Read from cache
const user = client.readFragment({
id: `User:${userId}`,
fragment: USER_FIELDS,
});
// Write to cache
client.writeFragment({
id: `User:${userId}`,
fragment: USER_FIELDS,
data: {
...user,
firstName: 'Updated Name',
},
});
Cursor-based pagination is recommended for large or rapidly changing data:
function useInfiniteUsers() {
const { data, loading, fetchMore } = useQuery(GET_USERS, {
variables: { first: 10 },
});
const loadMore = () => {
if (!data?.users.pageInfo.hasNextPage) return;
fetchMore({
variables: {
after: data.users.pageInfo.endCursor,
},
});
};
return {
users: data?.users.edges.map((edge) => edge.node) ?? [],
loading,
hasMore: data?.users.pageInfo.hasNextPage ?? false,
loadMore,
};
}
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: {
keyArgs: ['filter'],
merge(existing = { edges: [] }, incoming) {
return {
...incoming,
edges: [...existing.edges, ...incoming.edges],
};
},
},
},
},
},
});
Use batching techniques to reduce backend requests:
// Server-side with DataLoader
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (ids: string[]) => {
const users = await db.users.findMany({ where: { id: { in: ids } } });
return ids.map((id) => users.find((u) => u.id === id));
});
// In resolver
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId),
},
};
import { BatchHttpLink } from '@apollo/client/link/batch-http';
const batchLink = new BatchHttpLink({
uri: '/graphql',
batchMax: 10,
batchInterval: 20,
});
// Network only - skip cache
useQuery(GET_USER, {
fetchPolicy: 'network-only',
});
// Cache first - prefer cache
useQuery(GET_USER, {
fetchPolicy: 'cache-first',
});
// Cache and network - return cache, then update
useQuery(GET_USER, {
fetchPolicy: 'cache-and-network',
});
function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useUser(userId);
if (loading) return <Skeleton />;
if (error) {
return (
<ErrorMessage
message="Failed to load user profile"
retry={() => refetch()}
/>
);
}
return <ProfileCard user={data} />;
}
function CreateUserForm() {
const { createUser, loading, error } = useCreateUser({
onCompleted: (data) => {
if (data.createUser.errors?.length) {
// Handle validation errors
data.createUser.errors.forEach((err) => {
setFieldError(err.field, err.message);
});
} else {
// Success
toast.success('User created successfully');
}
},
});
// ...
}
For simple state requirements, use Apollo Client's local state management:
// Define local-only fields
const typeDefs = gql`
extend type Query {
isLoggedIn: Boolean!
cartItems: [CartItem!]!
}
`;
// Read local state
const IS_LOGGED_IN = gql`
query IsLoggedIn {
isLoggedIn @client
}
`;
// Write local state
client.writeQuery({
query: IS_LOGGED_IN,
data: { isLoggedIn: true },
});
For complex client-side state, consider using Zustand or Redux Toolkit alongside Apollo.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).