一键导入
apollo-graphql
Guidelines for developing GraphQL APIs and React applications using Apollo Client for state management, data fetching, and caching
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guidelines for developing GraphQL APIs and React applications using Apollo Client for state management, data fetching, and caching
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
A workflow for conducting rigorous, reproducible data investigations and ad-hoc analyses. This skill should be used when investigating a business question, explaining a metric anomaly, validating a hypothesis, performing root cause analysis, or preparing a one-off investigation dashboard or memo.
Drive the `airbyte-agent` CLI to manage Airbyte connectors, workspaces, and organizations. Run list/get/search/create/update actions against connector data (HubSpot, Salesforce, Slack, GitHub, etc.), install new connectors via the browser credential flow, list and switch workspaces, list organizations, inspect connector metadata, read skill docs, or print the merged CLI + OpenAPI schema for any operation. Use when the user mentions Airbyte, the `airbyte-agent` CLI, connectors, syncs, workspaces, organizations, or asks to read/write data from a connected SaaS product.
Queries, manages, and troubleshoots Apache Airflow using the af CLI. Covers listing DAGs, triggering runs, reading task logs, diagnosing failures, debugging DAG import errors, checking connections, variables, pools, and monitoring health. Also routes to sub-skills for writing DAGs, debugging, deploying, and migrating Airflow 2 to 3. Use when user mentions "Airflow", "DAG", "DAG run", "task log", "import error", "parse error", "broken DAG", or asks to "trigger a pipeline", "debug import errors", "check Airflow health", "list connections", "retry a run", or any Airflow operation. Do NOT use for warehouse/SQL analytics on Airflow metadata tables — use analyzing-data instead.
Implement dependency injection in Angular v20+ using inject(), injection tokens, and provider configuration. Use for service architecture, providing dependencies at different levels, creating injectable tokens, and managing singleton vs scoped services. Triggers on service creation, configuring providers, using injection tokens, or understanding DI hierarchy.
Create custom directives in Angular v20+ for DOM manipulation and behavior extension. Use for attribute directives that modify element behavior/appearance, structural directives for portals/overlays, and host directives for composition. Triggers on creating reusable DOM behaviors, extending element functionality, or composing behaviors across components. Note - use native @if/@for/@switch for control flow, not custom structural directives.
Build signal-based forms in Angular v21+ using the new Signal Forms API. Use for form creation with automatic two-way binding, schema-based validation, field state management, and dynamic forms. Triggers on form implementation, adding validation, creating multi-step forms, or building forms with conditional fields. Signal Forms are experimental but recommended for new Angular projects. Don't use for template-driven forms without signals or third-party form libraries like Formly or ngx-formly.
| name | apollo-graphql |
| description | Guidelines for developing GraphQL APIs and React applications using Apollo Client for state management, data fetching, and caching |
| metadata | {"category":"development","source":{"repository":"https://github.com/Mindrally/skills","path":"apollo-graphql","license_path":"LICENSE","commit":"05a71308897983093248d719a2ffa1bca61d0768"}} |
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.