apollo-caching-strategies

Apollo Caching Strategies

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "apollo-caching-strategies" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-apollo-caching-strategies

Apollo Caching Strategies

Master Apollo Client's caching mechanisms for building performant applications with optimal data fetching and state management strategies.

Overview

Apollo Client's intelligent cache is a normalized, in-memory data store that allows for efficient data fetching and updates. Understanding cache policies and management strategies is crucial for building high-performance apps.

Installation and Setup

Cache Configuration

// apollo/cache.js import { InMemoryCache, makeVar } from '@apollo/client';

export const cache = new InMemoryCache({ typePolicies: { Query: { fields: { posts: { // Pagination with offset keyArgs: ['filter'], merge(existing = [], incoming, { args }) { const merged = existing.slice(0); const offset = args?.offset || 0;

        for (let i = 0; i < incoming.length; i++) {
          merged[offset + i] = incoming[i];
        }

        return merged;
      }
    }
  }
},
Post: {
  keyFields: ['id'],
  fields: {
    comments: {
      merge(existing = [], incoming) {
        return [...existing, ...incoming];
      }
    }
  }
},
User: {
  keyFields: ['email'],
  fields: {
    fullName: {
      read(_, { readField }) {
        return `${readField('firstName')} ${readField('lastName')}`;
      }
    }
  }
}

} });

Core Patterns

  1. Fetch Policies

// Different fetch policies for different use cases import { useQuery } from '@apollo/client'; import { GET_POSTS } from './queries';

// cache-first (default): Check cache first, network if not found function CacheFirstPosts() { const { data } = useQuery(GET_POSTS, { fetchPolicy: 'cache-first' }); return <PostsList posts={data?.posts} />; }

// cache-only: Never make network request, cache or error function CacheOnlyPosts() { const { data } = useQuery(GET_POSTS, { fetchPolicy: 'cache-only' }); return <PostsList posts={data?.posts} />; }

// cache-and-network: Return cache immediately, update with network function CacheAndNetworkPosts() { const { data, loading, networkStatus } = useQuery(GET_POSTS, { fetchPolicy: 'cache-and-network', notifyOnNetworkStatusChange: true });

return ( <div> {networkStatus === 1 && <Spinner />} <PostsList posts={data?.posts} /> </div> ); }

// network-only: Always make network request, update cache function NetworkOnlyPosts() { const { data } = useQuery(GET_POSTS, { fetchPolicy: 'network-only' }); return <PostsList posts={data?.posts} />; }

// no-cache: Always make network request, don't update cache function NoCachePosts() { const { data } = useQuery(GET_POSTS, { fetchPolicy: 'no-cache' }); return <PostsList posts={data?.posts} />; }

// standby: Like cache-first but doesn't auto-update function StandbyPosts() { const { data, refetch } = useQuery(GET_POSTS, { fetchPolicy: 'standby' });

return ( <div> <button onClick={() => refetch()}>Refresh</button> <PostsList posts={data?.posts} /> </div> ); }

  1. Cache Reads and Writes

// apollo/cacheOperations.js import { gql } from '@apollo/client';

// Read from cache export function readPostFromCache(client, postId) { try { const data = client.readQuery({ query: gql query GetPost($id: ID!) { post(id: $id) { id title body } } , variables: { id: postId } }); return data?.post; } catch (error) { console.error('Post not in cache:', error); return null; } }

// Write to cache export function writePostToCache(client, post) { client.writeQuery({ query: gql query GetPost($id: ID!) { post(id: $id) { id title body } } , variables: { id: post.id }, data: { post } }); }

// Read fragment export function readPostFragment(client, postId) { return client.readFragment({ id: Post:${postId}, fragment: gql fragment PostFields on Post { id title body likesCount } }); }

// Write fragment export function updatePostLikes(client, postId, likesCount) { client.writeFragment({ id: Post:${postId}, fragment: gql fragment PostLikes on Post { likesCount } , data: { likesCount } }); }

// Modify cache fields export function incrementPostLikes(client, postId) { client.cache.modify({ id: client.cache.identify({ __typename: 'Post', id: postId }), fields: { likesCount(currentCount = 0) { return currentCount + 1; }, isLiked() { return true; } } }); }

  1. Optimistic Updates

// components/OptimisticLike.js import { useMutation } from '@apollo/client'; import { LIKE_POST } from '../mutations';

function OptimisticLike({ post }) { const [likePost] = useMutation(LIKE_POST, { variables: { postId: post.id },

// Optimistic response
optimisticResponse: {
  __typename: 'Mutation',
  likePost: {
    __typename: 'Post',
    id: post.id,
    likesCount: post.likesCount + 1,
    isLiked: true
  }
},

// Update cache
update(cache, { data: { likePost } }) {
  cache.modify({
    id: cache.identify(post),
    fields: {
      likesCount() {
        return likePost.likesCount;
      },
      isLiked() {
        return likePost.isLiked;
      }
    }
  });
},

// Handle errors
onError(error) {
  console.error('Like failed, reverting:', error);
  // Optimistic update automatically reverted
}

});

return ( <button onClick={() => likePost()}> {post.isLiked ? 'Unlike' : 'Like'} ({post.likesCount}) </button> ); }

// Complex optimistic update with multiple changes function OptimisticCreateComment({ postId }) { const [createComment] = useMutation(CREATE_COMMENT, { optimisticResponse: ({ body }) => ({ __typename: 'Mutation', createComment: { __typename: 'Comment', id: temp-${Date.now()}, body, createdAt: new Date().toISOString(), author: { __typename: 'User', id: currentUser.id, name: currentUser.name, avatar: currentUser.avatar } } }),

update(cache, { data: { createComment } }) {
  // Add comment to post
  cache.modify({
    id: cache.identify({ __typename: 'Post', id: postId }),
    fields: {
      comments(existing = []) {
        const newCommentRef = cache.writeFragment({
          data: createComment,
          fragment: gql`
            fragment NewComment on Comment {
              id
              body
              createdAt
              author {
                id
                name
                avatar
              }
            }
          `
        });
        return [...existing, newCommentRef];
      },
      commentsCount(count = 0) {
        return count + 1;
      }
    }
  });
}

});

return <CommentForm onSubmit={createComment} />; }

  1. Cache Eviction

// apollo/eviction.js export function evictPost(client, postId) { // Evict specific post client.cache.evict({ id: client.cache.identify({ __typename: 'Post', id: postId }) });

// Garbage collect client.cache.gc(); }

export function evictField(client, postId, fieldName) { // Evict specific field client.cache.evict({ id: client.cache.identify({ __typename: 'Post', id: postId }), fieldName }); }

export function evictAllPosts(client) { // Evict all posts from cache client.cache.modify({ fields: { posts(existing, { DELETE }) { return DELETE; } } });

client.cache.gc(); }

// Usage in delete mutation function DeletePost({ postId }) { const [deletePost] = useMutation(DELETE_POST, { variables: { id: postId },

update(cache) {
  // Remove from posts list
  cache.modify({
    fields: {
      posts(existingPosts = [], { readField }) {
        return existingPosts.filter(
          ref => postId !== readField('id', ref)
        );
      }
    }
  });

  // Evict post and related data
  cache.evict({ id: cache.identify({ __typename: 'Post', id: postId }) });
  cache.gc();
}

});

return <button onClick={() => deletePost()}>Delete</button>; }

  1. Reactive Variables

// apollo/reactiveVars.js import { makeVar, useReactiveVar } from '@apollo/client';

// Create reactive variables export const cartItemsVar = makeVar([]); export const themeVar = makeVar('light'); export const isModalOpenVar = makeVar(false); export const notificationsVar = makeVar([]);

// Helper functions export function addToCart(item) { const cart = cartItemsVar(); cartItemsVar([...cart, item]); }

export function removeFromCart(itemId) { const cart = cartItemsVar(); cartItemsVar(cart.filter(item => item.id !== itemId)); }

export function clearCart() { cartItemsVar([]); }

export function toggleTheme() { const current = themeVar(); themeVar(current === 'light' ? 'dark' : 'light'); }

export function addNotification(notification) { const notifications = notificationsVar(); notificationsVar([...notifications, { id: Date.now(), ...notification }]); }

// React component usage function Cart() { const cartItems = useReactiveVar(cartItemsVar);

return ( <div> <h2>Cart ({cartItems.length})</h2> {cartItems.map(item => ( <div key={item.id}> {item.name} <button onClick={() => removeFromCart(item.id)}>Remove</button> </div> ))} </div> ); }

// Use in cache configuration const cache = new InMemoryCache({ typePolicies: { Query: { fields: { cartItems: { read() { return cartItemsVar(); } }, theme: { read() { return themeVar(); } } } } } });

  1. Pagination Strategies

// Offset-based pagination const POSTS_QUERY = gql query GetPosts($limit: Int!, $offset: Int!) { posts(limit: $limit, offset: $offset) { id title body } };

function OffsetPagination() { const { data, fetchMore } = useQuery(POSTS_QUERY, { variables: { limit: 10, offset: 0 } });

return ( <div> <PostsList posts={data?.posts} /> <button onClick={() => fetchMore({ variables: { offset: data.posts.length } }) } > Load More </button> </div> ); }

// Cursor-based pagination const CURSOR_POSTS_QUERY = gql query GetPosts($first: Int!, $after: String) { posts(first: $first, after: $after) { edges { cursor node { id title body } } pageInfo { hasNextPage endCursor } } };

function CursorPagination() { const { data, fetchMore } = useQuery(CURSOR_POSTS_QUERY, { variables: { first: 10 } });

return ( <div> {data?.posts.edges.map(({ node }) => ( <Post key={node.id} post={node} /> ))}

  {data?.posts.pageInfo.hasNextPage &#x26;&#x26; (
    &#x3C;button
      onClick={() =>
        fetchMore({
          variables: {
            after: data.posts.pageInfo.endCursor
          }
        })
      }
    >
      Load More
    &#x3C;/button>
  )}
&#x3C;/div>

); }

// Cache configuration for pagination const cache = new InMemoryCache({ typePolicies: { Query: { fields: { posts: { keyArgs: ['filter'], merge(existing, incoming, { args }) { if (!existing) return incoming;

        const { offset = 0 } = args;
        const merged = existing.slice(0);

        for (let i = 0; i &#x3C; incoming.length; i++) {
          merged[offset + i] = incoming[i];
        }

        return merged;
      }
    }
  }
}

} });

// Relay-style pagination with offsetLimitPagination import { offsetLimitPagination } from '@apollo/client/utilities';

const cache = new InMemoryCache({ typePolicies: { Query: { fields: { posts: offsetLimitPagination() } } } });

  1. Cache Persistence

// apollo/persistedCache.js import { InMemoryCache } from '@apollo/client'; import { persistCache, LocalStorageWrapper } from 'apollo3-cache-persist';

export async function createPersistedCache() { const cache = new InMemoryCache({ typePolicies: { // Your type policies } });

await persistCache({ cache, storage: new LocalStorageWrapper(window.localStorage), maxSize: 1048576, // 1 MB debug: true, trigger: 'write', // or 'background' });

return cache; }

// Usage in client setup import { ApolloClient } from '@apollo/client';

async function initApollo() { const cache = await createPersistedCache();

const client = new ApolloClient({ uri: 'http://localhost:4000/graphql', cache });

return client; }

// Clear persisted cache export function clearPersistedCache(client) { client.clearStore(); // Clears cache localStorage.clear(); // Clears persistence }

// Selective persistence const cache = new InMemoryCache({ typePolicies: { User: { fields: { // Don't persist sensitive data authToken: { read() { return null; } } } } } });

  1. Cache Warming

// apollo/cacheWarming.js import { gql } from '@apollo/client';

export async function warmCache(client) { // Preload critical queries await Promise.all([ client.query({ query: gql query GetCurrentUser { me { id name email } } }),

client.query({
  query: gql`
    query GetRecentPosts {
      posts(limit: 20) {
        id
        title
        excerpt
      }
    }
  `
})

]); }

// Prefetch on hover function PostLink({ postId }) { const client = useApolloClient();

const prefetch = () => { client.query({ query: GET_POST, variables: { id: postId } }); };

return ( <Link to={/posts/${postId}} onMouseEnter={prefetch} onTouchStart={prefetch} > View Post </Link> ); }

  1. Cache Redirects

// apollo/cache.js const cache = new InMemoryCache({ typePolicies: { Query: { fields: { post: { read(_, { args, toReference }) { // Redirect to cached object return toReference({ __typename: 'Post', id: args.id }); } } } },

User: {
  fields: {
    // Computed field from cache
    fullName: {
      read(_, { readField }) {
        const firstName = readField('firstName');
        const lastName = readField('lastName');
        return `${firstName} ${lastName}`;
      }
    },

    // Field with arguments
    posts: {
      read(existing, { args, readField }) {
        if (args?.published !== undefined) {
          return existing?.filter(ref =>
            readField('published', ref) === args.published
          );
        }
        return existing;
      }
    }
  }
}

} });

  1. Cache Monitoring and Debugging

// apollo/monitoring.js export function logCacheContents(client) { const cache = client.extract(); console.log('Cache contents:', cache); }

export function watchCacheChanges(client) { const observer = client.cache.watch({ query: gql query GetAllData { posts { id title } } , callback: (data) => { console.log('Cache changed:', data); } });

return observer; }

// Development helpers if (process.env.NODE_ENV === 'development') { window.apolloClient = client; window.logCache = () => logCacheContents(client);

// Cache size monitoring setInterval(() => { const cacheSize = JSON.stringify(client.extract()).length; console.log(Cache size: ${(cacheSize / 1024).toFixed(2)} KB); }, 10000); }

// React DevTools integration import { ApolloClient } from '@apollo/client'; import { ApolloProvider } from '@apollo/client/react';

function App() { return ( <ApolloProvider client={client}> {/* Enable Apollo DevTools */} <YourApp /> </ApolloProvider> ); }

// Custom cache inspector function CacheInspector() { const client = useApolloClient(); const [cacheData, setCacheData] = useState({});

useEffect(() => { const data = client.extract(); setCacheData(data); }, [client]);

return ( <div> <h2>Cache Inspector</h2> <pre>{JSON.stringify(cacheData, null, 2)}</pre> <button onClick={() => client.clearStore()}>Clear Cache</button> </div> ); }

Best Practices

  • Choose appropriate fetch policies - Match policy to data freshness needs

  • Use optimistic updates - Improve perceived performance

  • Normalize cache properly - Configure keyFields correctly

  • Implement pagination - Handle large datasets efficiently

  • Persist critical data - Cache auth state and user preferences

  • Monitor cache size - Prevent memory bloat

  • Use reactive variables - Manage local state efficiently

  • Warm cache strategically - Prefetch critical data

  • Evict unused data - Clean up after deletions

  • Debug cache issues - Use Apollo DevTools effectively

Common Pitfalls

  • Wrong fetch policy - Using cache-first for real-time data

  • Cache denormalization - Missing or incorrect keyFields

  • Memory leaks - Not evicting deleted items

  • Over-caching - Caching too much data

  • Stale data - Not invalidating cache properly

  • Missing updates - Forgetting to update cache after mutations

  • Incorrect merges - Wrong pagination merge logic

  • Cache thrashing - Too many cache writes

  • Persistence issues - Storing sensitive data

  • No error handling - Not handling cache read failures

When to Use

  • Building data-intensive applications

  • Implementing offline-first features

  • Creating real-time collaborative apps

  • Developing mobile applications

  • Building e-commerce platforms

  • Creating social media applications

  • Implementing complex state management

  • Developing admin dashboards

  • Building content management systems

  • Creating analytics applications

Resources

  • Apollo Cache Documentation

  • Advanced Cache Patterns

  • Cache Persistence

  • Apollo DevTools

  • Optimistic UI Guide

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review