# Apollo Client Tips & Tools

Tips and patterns for working with Apollo Client in OrchardGo.

## Cache Management

### Disable Cache Persistence

Cache persistence can make debugging difficult when the cache grows large over time.

To disable temporarily, edit `src/config.js`:

```js
export const ENABLE_APOLLO_CACHE_PERSIST = false;
```

**Remember to re-enable after debugging:**

```js
export const ENABLE_APOLLO_CACHE_PERSIST = true;
```

### Clear Cache

```typescript
import { useApolloClient } from '@apollo/client';

const MyComponent = () => {
  const client = useApolloClient();

  const clearCache = async () => {
    await client.clearStore();
    // or
    await client.resetStore(); // Clears and refetches active queries
  };
};
```

## Debugging Cache

### Logging Cache Entities

Use `logEntity` to inspect normalized cache entities before and after updates:

```typescript
import { logEntity } from '@/apollo/tools';
import { useMutation } from '@apollo/client';

const useMyHook = () => {
  const [mutation] = useMutation(MY_MUTATION, {
    update: (cache, { data }) => {
      // Log before modification
      logEntity('Participant', id);

      cache.modify({
        id: cache.identify({ __typename: 'Participant', id }),
        fields: {
          status: () => 'ACTIVE',
        },
      });

      // Log after modification
      logEntity('Participant', id);
    },
  });
};
```

### Inspect Cache in DevTools

```typescript
import { useApolloClient } from '@apollo/client';

const MyComponent = () => {
  const client = useApolloClient();

  // Log entire cache
  console.log('Apollo Cache:', client.cache.extract());

  // Log specific entity
  const entity = client.cache.readFragment({
    id: 'Participant:123',
    fragment: gql`
      fragment ParticipantData on Participant {
        id
        name
        status
      }
    `,
  });
  console.log('Entity:', entity);
};
```

## Query Patterns

### Polling

```typescript
const { data, startPolling, stopPolling } = useQuery(MY_QUERY, {
  pollInterval: 5000, // Poll every 5 seconds
});

// Control polling
useEffect(() => {
  startPolling(5000);
  return () => stopPolling();
}, []);
```

### Fetch Policy

```typescript
// Cache first (default)
useQuery(MY_QUERY, {
  fetchPolicy: 'cache-first',
});

// Network only
useQuery(MY_QUERY, {
  fetchPolicy: 'network-only',
});

// Cache only
useQuery(MY_QUERY, {
  fetchPolicy: 'cache-only',
});

// Cache and network
useQuery(MY_QUERY, {
  fetchPolicy: 'cache-and-network',
});
```

### Error Handling

```typescript
const { data, error, loading } = useQuery(MY_QUERY, {
  onError: (error) => {
    console.error('Query error:', error);
    // Handle error
  },
  errorPolicy: 'all', // Return partial data on error
});
```

## Mutation Patterns

### Optimistic Response

```typescript
const [updateUser] = useMutation(UPDATE_USER, {
  optimisticResponse: {
    __typename: 'Mutation',
    updateUser: {
      __typename: 'User',
      id: userId,
      name: newName,
    },
  },
});
```

### Update Cache After Mutation

```typescript
const [addItem] = useMutation(ADD_ITEM, {
  update: (cache, { data: { addItem } }) => {
    // Read existing query
    const existing = cache.readQuery({ query: GET_ITEMS });

    // Write updated query
    cache.writeQuery({
      query: GET_ITEMS,
      data: {
        items: [...existing.items, addItem],
      },
    });
  },
});
```

### Refetch Queries

```typescript
const [deleteItem] = useMutation(DELETE_ITEM, {
  refetchQueries: [
    { query: GET_ITEMS },
    { query: GET_ITEM_COUNT },
  ],
});
```

## Cache Modification

### Modify Specific Fields

```typescript
cache.modify({
  id: cache.identify({ __typename: 'User', id: '123' }),
  fields: {
    name: (name) => 'New Name',
    followers: (existing = []) => [...existing, newFollower],
  },
});
```

### Evict from Cache

```typescript
// Evict specific entity
cache.evict({ id: cache.identify({ __typename: 'User', id: '123' }) });

// Evict specific field
cache.evict({
  id: cache.identify({ __typename: 'User', id: '123' }),
  fieldName: 'followers',
});

// Garbage collect
cache.gc();
```

## Fragment Usage

### Define Fragment

```typescript
const USER_FRAGMENT = gql`
  fragment UserData on User {
    id
    name
    email
    avatar
  }
`;
```

### Use in Query

```typescript
const GET_USER = gql`
  ${USER_FRAGMENT}
  query GetUser($id: ID!) {
    user(id: $id) {
      ...UserData
      posts {
        id
        title
      }
    }
  }
`;
```

### Read Fragment from Cache

```typescript
const userData = cache.readFragment({
  id: cache.identify({ __typename: 'User', id: '123' }),
  fragment: USER_FRAGMENT,
});
```

### Write Fragment to Cache

```typescript
cache.writeFragment({
  id: cache.identify({ __typename: 'User', id: '123' }),
  fragment: USER_FRAGMENT,
  data: {
    __typename: 'User',
    id: '123',
    name: 'New Name',
    email: 'new@email.com',
    avatar: 'avatar.png',
  },
});
```

## Type Policies

Type policies are configured in Apollo Client setup:

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    User: {
      keyFields: ['id'],
      fields: {
        fullName: {
          read(_, { readField }) {
            const firstName = readField('firstName');
            const lastName = readField('lastName');
            return `${firstName} ${lastName}`;
          },
        },
      },
    },
    Query: {
      fields: {
        items: {
          merge(existing = [], incoming) {
            return [...existing, ...incoming];
          },
        },
      },
    },
  },
});
```

## Performance Tips

### 1. Use Fragments

Reuse fragments to reduce query size and ensure consistency:

```typescript
const USER_CORE = gql`
  fragment UserCore on User {
    id
    name
    email
  }
`;

// Reuse in multiple queries
const GET_USER = gql`
  ${USER_CORE}
  query GetUser($id: ID!) {
    user(id: $id) {
      ...UserCore
      posts { ... }
    }
  }
`;
```

### 2. Batch Queries

Use `@client` directive for local fields:

```typescript
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      isSelected @client
    }
  }
`;
```

### 3. Defer Non-Critical Data

Use `@defer` for non-critical data (if supported by backend):

```typescript
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      ... @defer {
        posts {
          id
          title
        }
      }
    }
  }
`;
```

### 4. Pagination

Implement cursor-based pagination:

```typescript
const { data, fetchMore } = useQuery(GET_ITEMS, {
  variables: { first: 20, after: null },
});

const loadMore = () => {
  fetchMore({
    variables: {
      after: data.items.pageInfo.endCursor,
    },
  });
};
```

## Common Issues

### "Missing field" Warnings

Add field policies or fetch missing fields:

```typescript
typePolicies: {
  User: {
    fields: {
      missingField: {
        read() {
          return null; // Default value
        },
      },
    },
  },
}
```

### Cache Not Updating

1. Check entity has `__typename` and `id`
2. Verify cache update logic in mutation
3. Use `refetchQueries` as fallback

### Memory Leaks

1. Stop polling on unmount
2. Clear cache periodically
3. Disable cache persistence for debug

## Next Steps

- [Authentication Flow](./authentication.md)
- [GraphQL Best Practices](../architecture/graphql-patterns.md)
- [Testing Apollo](../development/testing.md)
