All skills
Skillintermediate

GraphQL Schema Patterns

1. **NEVER inline `gql` literals** - Create `.gql` files 2. **ALWAYS run codegen** after creating/modifying `.gql` files 3. **ALWAYS add `onError` handler** to mutations 4. **Use generated hooks** - Never write raw Apollo hooks

Claude Code Knowledge Pack7/10/2026

Overview

GraphQL Schema Patterns

Core Rules

  1. NEVER inline gql literals - Create .gql files
  2. ALWAYS run codegen after creating/modifying .gql files
  3. ALWAYS add onError handler to mutations
  4. Use generated hooks - Never write raw Apollo hooks

File Structure

src/
├── components/
│   └── ItemList/
│       ├── ItemList.tsx
│       ├── GetItems.gql           # Query definition
│       └── GetItems.generated.ts  # Auto-generated (don't edit)
└── graphql/
    └── mutations/
        └── CreateItem.gql         # Shared mutations

Creating a Query

Step 1: Create .gql file

# src/components/ItemList/GetItems.gql
query GetItems($limit: Int, $offset: Int) {
  items(limit: $limit, offset: $offset) {
    id
    name
    description
    createdAt
  }
}

Step 2: Run codegen

npm run gql:typegen

Step 3: Import and use generated hook


const ItemList = () => {
  const { data, loading, error, refetch } = useGetItemsQuery({
    variables: { limit: 20, offset: 0 },
  });

  if (error) return ;
  if (loading && !data) return ;
  if (!data?.items.length) return ;

  return ;
};

Creating a Mutation

Step 1: Create .gql file

# src/graphql/mutations/CreateItem.gql
mutation CreateItem($input: CreateItemInput!) {
  createItem(input: $input) {
    id
    name
    description
  }
}

Step 2: Run codegen

npm run gql:typegen

Step 3: Use with REQUIRED error handling


const CreateItemForm = () => {
  const [createItem, { loading }] = useCreateItemMutation({
    // Success handling
    onCompleted: (data) => {
      toast.success({ title: 'Item created' });
      navigation.goBack();
    },
    // ERROR HANDLING IS REQUIRED
    onError: (error) => {
      console.error('createItem failed:', error);
      toast.error({ title: 'Failed to create item' });
    },
    // Cache update
    update: (cache, { data }) => {
      if (data?.createItem) {
        cache.modify({
          fields: {
            items: (existing = []) => [...existing, data.createItem],
          },
        });
      }
    },
  });

  return (
     createItem({ variables: { input: formValues } })}
      isDisabled={!isValid || loading}
      isLoading={loading}
    >
      Create
    
  );
};

Mutation UI Requirements

CRITICAL: Every mutation trigger must:

  1. Be disabled during mutation - Prevent double-clicks
  2. Show loading state - Visual feedback
  3. Have onError handler - User knows it failed
  4. Show success feedback - User knows it worked
// CORRECT - Complete mutation pattern
const [submit, { loading }] = useSubmitMutation({
  onError: (error) => {
    console.error('submit failed:', error);
    toast.error({ title: 'Save failed' });
  },
  onCompleted: () => {
    toast.success({ title: 'Saved' });
  },
});

  Submit

Query Options

Fetch Policies

PolicyUse When
cache-firstData rarely changes
cache-and-networkWant fast + fresh (default)
network-onlyAlways need latest
no-cacheNever cache (rare)

Common Options

useGetItemsQuery({
  variables: { id: itemId },

  // Fetch strategy
  fetchPolicy: 'cache-and-network',

  // Re-render on network status changes
  notifyOnNetworkStatusChange: true,

  // Skip if condition not met
  skip: !itemId,

  // Poll for updates
  pollInterval: 30000,
});

Optimistic Updates

For instant UI feedback:

const [toggleFavorite] = useToggleFavoriteMutation({
  optimisticResponse: {
    toggleFavorite: {
      __typename: 'Item',
      id: itemId,
      isFavorite: !currentState,
    },
  },
  onError: (error) => {
    // Rollback happens automatically
    console.error('toggleFavorite failed:', error);
    toast.error({ title: 'Failed to update' });
  },
});

When NOT to Use Optimistic Updates

  • Operations that can fail validation
  • Operations with server-generated values
  • Destructive operations (delete)
  • Operations affecting other users

Fragments

For reusable field selections:

# src/graphql/fragments/ItemFields.gql
fragment ItemFields on Item {
  id
  name
  description
  createdAt
  updatedAt
}

Use in queries:

query GetItems {
  items {
    ...ItemFields
  }
}

Anti-Patterns

// WRONG - Inline gql
const GET_ITEMS = gql`
  query GetItems { items { id } }
`;

// CORRECT - Use .gql file + generated hook

// WRONG - No error handler
const [mutate] = useMutation(MUTATION);

// CORRECT - Always handle errors
const [mutate] = useMutation(MUTATION, {
  onError: (error) => {
    console.error('mutation failed:', error);
    toast.error({ title: 'Operation failed' });
  },
});

// WRONG - Button not disabled during mutation
Submit

// CORRECT - Disabled and loading

  Submit

Codegen Commands

# Generate types from .gql files
npm run gql:typegen

# Download schema + generate types
npm run sync-types

Integration with Other Skills

  • react-ui-patterns: Loading/error/empty states for queries
  • testing-patterns: Mock generated hooks in tests
  • formik-patterns: Mutation submission patterns