From harness-claude
> Structure GraphQL client code with fragments, cache normalization, and optimistic updates for responsive UIs
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Structure GraphQL client code with fragments, cache normalization, and optimistic updates for responsive UIs
Implements Apollo Client patterns for GraphQL queries, mutations, cache management, and local state in React applications.
Guides Apollo Client 4.x integration in React apps (Next.js, Vite, CRA), GraphQL queries/mutations with hooks, cache config, local state via reactive vars, and troubleshooting.
Guides GraphQL schema design, resolvers, DataLoader for N+1 prevention, federation, subscriptions, and Apollo/urql clients. Use for efficient APIs with complex data relationships.
Share bugs, ideas, or general feedback.
Structure GraphQL client code with fragments, cache normalization, and optimistic updates for responsive UIs
.graphql file) as the component that renders the data. This makes data dependencies explicit.const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
...UserAvatar
}
}
${USER_AVATAR_FRAGMENT}
`;
function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useQuery(GET_USER, { variables: { id: userId } });
if (loading) return <Skeleton />;
if (error) return <ErrorBanner error={error} />;
return <div>{data.user.name}</div>;
}
const USER_AVATAR_FRAGMENT = gql`
fragment UserAvatar on User {
id
avatarUrl
name
}
`;
// Reuse in any query that needs avatar data
typePolicies. Apollo Client's InMemoryCache normalizes by __typename:id. Customize merge behavior, pagination, and key fields in typePolicies.const cache = new InMemoryCache({
typePolicies: {
User: {
keyFields: ['id'],
},
Query: {
fields: {
feed: offsetLimitPagination(),
},
},
},
});
optimisticResponse that mirrors the expected server response. The cache updates immediately; the real response replaces it when it arrives.const [toggleLike] = useMutation(TOGGLE_LIKE, {
optimisticResponse: {
toggleLike: {
__typename: 'Post',
id: postId,
isLiked: !currentlyLiked,
likeCount: currentlyLiked ? count - 1 : count + 1,
},
},
});
id and __typename, the cache updates automatically.refetchQueries: Re-execute specific queries after the mutation. Simple but costs a network round-trip.update function: Manually read and write the cache for complex updates (e.g., adding an item to a list).const [addComment] = useMutation(ADD_COMMENT, {
update(cache, { data: { addComment } }) {
cache.modify({
id: cache.identify({ __typename: 'Post', id: postId }),
fields: {
comments(existing = []) {
const newRef = cache.writeFragment({
data: addComment,
fragment: COMMENT_FIELDS,
});
return [...existing, newRef];
},
},
});
},
});
Handle loading and error states consistently. Create reusable patterns — a <QueryResult> wrapper component or custom hooks that standardize loading/error/empty states across the app.
Use fetchPolicy intentionally.
cache-first (default): Read from cache, fetch only on miss. Best for stable data.network-only: Always fetch, update cache. Best for frequently changing data.cache-and-network: Return cached data immediately, then update from network. Best UX for lists.no-cache: Skip the cache entirely. Use for one-off sensitive data.Avoid over-fetching with field-level selections. Only request the fields the component needs. The normalized cache works best when queries request predictable field sets.
Apollo Client vs. urql vs. lightweight clients: Apollo Client offers the richest cache and ecosystem but is the largest bundle (~40KB). urql is smaller (~15KB) with a plugin-based architecture (exchanges). For simple use cases, graphql-request (~5KB) provides a fetch wrapper without caching.
Cache normalization explained: Apollo splits query results into individual objects keyed by __typename:id. When a mutation returns an updated User, every query that references that user sees the update. This works only if every queried type has a stable id field.
Fragment colocation pattern (Relay-style): Each component declares a fragment for the data it needs. Parent components spread those fragments into their queries. This creates a clear contract: the component works if and only if its fragment is included.
Polling vs. subscriptions: Use pollInterval for data that changes infrequently (dashboard stats). Use subscriptions for real-time data (chat messages, live scores). Polling is simpler to implement and does not require WebSocket infrastructure.
Common mistakes:
__typename in optimistic responses (cache cannot normalize without it)refetchQueries for every mutation instead of leveraging automatic cache updateserror state from useQuery (silent failures)https://www.apollographql.com/docs/react/