javascripttypescriptgraphqlapollotypepolicies

Apollo GraphQL updateQuery to typePolicy


I am beating my head against a wall. I have updated to Apollo 3, and cannot figure out how to migrate an updateQuery to a typePolicy. I am doing basic continuation based pagination, and this is how I used to merged the results of fetchMore:

await fetchMore({
  query: MessagesByThreadIDQuery,
  variables: {
    threadId: threadId,
    limit: Configuration.MessagePageSize,
    continuation: token
  },
  updateQuery: (prev, curr) => {
    // Extract our updated message page.
    const last = prev.messagesByThreadId.messages ?? []
    const next = curr.fetchMoreResult?.messagesByThreadId.messages ?? []

    return {
      messagesByThreadId: {
        __typename: 'MessagesContinuation',
        messages: [...last, ...next],
        continuation: curr.fetchMoreResult?.messagesByThreadId.continuation
      }
    }
  }

I have made an attempt to write the merge typePolicy myself, but it just continually loads and throws errors about duplicate identifiers in the Apollo cache. Here is what my typePolicy looks like for my query.

  typePolicies: {
    Query: {
      fields: {
        messagesByThreadId: {
          keyArgs: false,
          merge: (existing, incoming, args): IMessagesContinuation => {
            const typedExisting: IMessagesContinuation | undefined = existing
            const typedIncoming: IMessagesContinuation | undefined = incoming
            const existingMessages = (typedExisting?.messages ?? [])
            const incomingMessages = (typedIncoming?.messages ?? [])

            const result = existing ? {
              __typename: 'MessageContinuation',
              messages: [...existingMessages, ...incomingMessages],
              continuation: typedIncoming?.continuation
            } : incoming


            return result
          }
        }
      }
    }
  }

Solution

  • So I was able to solve my use-case. It seems way harder than it really needs to be. I essentially have to attempt to locate existing items matching the incoming and overwrite them, as well as add any new items that don't yet exist in the cache.

    I also have to only apply this logic if a continuation token was provided, because if it's null or undefined, I should just use the incoming value because that indicates that we are doing an initial load.

    My document is shaped like this:

    {
      "items": [{ id: string, ...others }],
      "continuation": "some_token_value"
    }
    

    I created a generic type policy that I can use for all my documents that have a similar shape. It allows me to specify the name of the items property, what the key args are that I want to cache on, and the name of the graphql type.

    export function ContinuationPolicy(keyArgs: Array<string>, itemPropertyKey: string, typeName: string) {
      return {
        keyArgs,
        merge(existing: any, incoming: any, args: any) {
          if (!!existing && !!args.args?.continuation) {
            const existingItems = (existing ? existing[itemPropertyKey] : [])
            const incomingItems = (incoming ? incoming[itemPropertyKey] : [])
            let items: Array<any> = [...existingItems]
    
            for (let i = 0; i < incomingItems.length; i++) {
              const current = incomingItems[i] as any
              const found = items.findIndex(m => m.__ref === current.__ref)
    
              if (found > -1) {
                items[found] === current
              } else {
                items = [...items, current]
              }
            }
    
            // This new data is a continuation of the last data.
            return {
              __typename: typeName,
              [itemPropertyKey]: items,
              continuation: incoming.continuation
            }
          } else {
            // When we have no existing data in the cache, we'll just use the incoming data.
            return incoming
          }
        }
      }
    }