Jeffrey Hicks

Jeffrey Hicks

Platform Eng @R360

Apollo Client: Sync vs Refetch Patterns

Research exploring Apollo Client's sync-oriented architecture versus traditional refetch patterns in GraphQL state management

By Jeffrey Hicks • Aug 22, 2025 • analysis

Apollo Client operates primarily on a synchronization model rather than a “post-and-re-download” pattern. This fundamental difference sets it apart from other data fetching approaches.

Apollo Client: Sync-Oriented Architecture

The Core Sync Mechanism

1. Normalized Cache as Single Source of Truth Apollo maintains a normalized cache that acts as a local replica of your server state:

// When you fetch data, it gets normalized into the cache
const { data } = useQuery(GET_USER, { variables: { id: 1 } });

// Cache structure after normalization:
{
  "User:1": {
    "id": 1,
    "name": "John",
    "__typename": "User"
  },
  "ROOT_QUERY": {
    "user({id:1})": { "__ref": "User:1" }
  }
}

2. Mutations Update Cache Directly When you perform mutations, Apollo synchronizes the cache rather than refetching:

const [updateUser] = useMutation(UPDATE_USER, {
  update(cache, { data: { updateUser } }) {
    // Direct cache synchronization - no refetch needed
    cache.modify({
      id: cache.identify(updateUser),
      fields: {
        name() { return updateUser.name; }
      }
    });
  }
});

Three Sync Strategies in Apollo

StrategyWhen UsedHow It Works
Automatic SyncEntity mutations with IDsApollo automatically updates normalized entities
Manual Cache UpdatesList operations, complex changesUse update function to modify cache directly
Optimistic SyncImmediate UI feedbackUpdate cache immediately, reconcile with server later

Sync vs Refetch: The Key Difference

Apollo’s Sync Approach (Preferred)

// ✅ Sync pattern - Apollo's strength
const [addTodo] = useMutation(ADD_TODO, {
  update(cache, { data: { addTodo } }) {
    // Synchronize cache with new data
    const { todos } = cache.readQuery({ query: GET_TODOS });
    cache.writeQuery({
      query: GET_TODOS,
      data: { todos: [...todos, addTodo] }
    });
  }
});

// Result: Instant UI update, no network request

Traditional Refetch Approach (Less Optimal)

// ⚠️ Refetch pattern - works but defeats Apollo's purpose
const [addTodo] = useMutation(ADD_TODO, {
  refetchQueries: [{ query: GET_TODOS }] // Forces new network request
});

// Result: Network delay, but simpler logic

When Apollo Uses Each Approach

Automatic Synchronization (Most Common)

Happens automatically when:

  • Mutation returns entities with id and __typename
  • Returned entities match existing cache entries
  • No complex list operations involved
// This automatically syncs - no update function needed
const UPDATE_USER_NAME = gql`
  mutation UpdateUserName($id: ID!, $name: String!) {
    updateUser(id: $id, name: $name) {
      id          # ✅ Required for auto-sync
      name        # ✅ Will automatically update in cache
      __typename  # ✅ Added automatically
    }
  }
`;

Manual Cache Synchronization

Required for:

  • Adding/removing items from lists
  • Complex nested updates
  • Creating new entities
const [addComment] = useMutation(ADD_COMMENT, {
  update(cache, { data: { addComment } }) {
    // Manual sync: add comment to post's comment list
    cache.modify({
      id: cache.identify({ __typename: 'Post', id: postId }),
      fields: {
        comments(existingComments = []) {
          const newCommentRef = cache.writeFragment({
            data: addComment,
            fragment: COMMENT_FRAGMENT
          });
          return [...existingComments, newCommentRef];
        }
      }
    });
  }
});

Optimistic Synchronization

For instant UI feedback:

const [likePost] = useMutation(LIKE_POST, {
  optimisticResponse: {
    likePost: {
      __typename: 'Post',
      id: postId,
      likesCount: currentLikes + 1,  // Optimistic update
      isLiked: true
    }
  }
  // Cache syncs immediately with optimistic data
  // Then reconciles with real server response
});

Why Sync Over Refetch?

Benefits of Apollo’s Sync Approach

Instant UI Updates: No network round-trip delay ✅ Efficient: Avoids unnecessary data transfer ✅ Consistent: Single source of truth in cache ✅ Automatic Propagation: Updates appear everywhere the data is used

When Refetch Makes Sense

⚠️ Complex server-side calculations that can’t be predicted client-side ⚠️ Simple applications where sync complexity isn’t worth it ⚠️ Error scenarios to ensure consistency

// Sometimes refetch is simpler
const [complexCalculation] = useMutation(COMPLEX_MUTATION, {
  refetchQueries: [{ query: GET_DASHBOARD_STATS }]
  // Easier than trying to manually calculate new stats
});

The Mental Model Shift

Traditional REST Pattern (React Query)

Action → Server → Re-fetch Data → Update UI

Apollo GraphQL Pattern

Action → Sync Cache → Update UI → (Server confirms in background)

Real-World Example: Social Media App

Apollo Sync Approach

// Like a post - instant feedback
const [likePost] = useMutation(LIKE_POST, {
  optimisticResponse: { 
    likePost: { id: postId, isLiked: true, likesCount: likes + 1 }
  }
  // UI updates instantly, all components using this post sync automatically
});

Traditional Refetch Approach

// Like a post - wait for server
const [likePost] = useMutation(LIKE_POST, {
  refetchQueries: [GET_POSTS, GET_POST_DETAILS]
  // UI waits for network, multiple queries refetch
});

Conclusion

Apollo Client is fundamentally designed around cache synchronization rather than refetching. You’re interacting with the server through:

  1. Sync-first: Cache updates immediately reflect in UI
  2. Server reconciliation: Background confirmation/correction
  3. Optimistic updates: Assume success, handle failures gracefully

This sync-oriented approach is Apollo’s superpower - it makes apps feel instant while maintaining consistency with the server. The refetch approach works but doesn’t leverage Apollo’s core strengths and can make your app feel slower and less efficient.

References

  1. Apollo GraphQL Mutations Documentation
    https://www.apollographql.com/docs/react/data/mutations

  2. Keeping Apollo Cache Up to Date After Mutations
    https://www.splitgraph.com/blog/keeping-apollo-cache-up-to-date-after-mutations

  3. Apollo Client Caching Overview
    https://www.apollographql.com/docs/react/caching/overview

  4. How to Update the Apollo Client’s Cache After a Mutation
    https://www.freecodecamp.org/news/how-to-update-the-apollo-clients-cache-after-a-mutation-79a0df79b840/

  5. Manually Update Apollo Cache After GraphQL Mutation
    https://curiosum.com/til/manually-update-apollo-cache-after-graphql-mutation

  6. Understanding Apollo Client Cache: Nested Data Structures
    https://dev.to/bhanufyi/understanding-apollo-client-cache-how-to-manage-and-update-nested-data-structures-effectively-11n8

  7. Optimistic Updates in Apollo Client React
    https://www.linkedin.com/pulse/optimistic-updation-apollo-client-react-antematter-5wthc

  8. Apollo Client Refetching Documentation
    https://www.apollographql.com/docs/react/data/refetching

  9. GraphQL Caching and Micro Frontends
    https://adamrackis.dev/blog/graphql-caching-and-micro

  10. Best Practice Libraries for Maintaining Local State
    https://www.reddit.com/r/webdev/comments/vz6nq2/best_practice_libraries_for_maintaining_local/