Nested pagination - UI does not re-render - Cache IS updated

Hello! Thanks for any pointers and help – I am trying to get some nested pagination working - It seems I can get the data into the cache in the right place, but the UI does not update.

First, I request a list of paginated “Kits” from the server with the following query. Nested within the kits is a list of “Products” that is also paginated. The two inputs/variables below handle the cursor based pagination with the createdAt field.

export const GET_KIT_PRODUCTS = gql`
  query GetKitProducts(
    $getKitsInput: GetKitsInput!
    $getProductsInput: GetProductsInput!
  ) {
    getKits(getKitsInput: $getKitsInput) {
      data {
        _id
        products(getProductsInput: $getProductsInput) {
          stats {
            total
            remaining
            page
          }
          data {
            _id
            name
            brand_name
            retailer
            createdAt
          }
        }
      }
    }
  }
`;

The server sends back a list of kits and the correct pagination for both the kits and the products. I use the following TypePolicy to make sure duplicate kits and products are not added to the cache. The products are also merged under the correct kit.

Also for reference, here is the GetKitsResponse type. The Kit list is actually nested inside the data property of the response.

  type GetKitsResponse {
    stats: Stats
    data: [Kit]!
  }

export const cache: InMemoryCache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        getKits: {
          keyArgs: false,
          merge(
            existing = [] as Kit[],
            incoming: GetKitsResponse,
            { args, readField }
          ) {
            console.log("Existing", existing);
            console.log("Incoming", incoming);

            const merged = { ...existing };

            incoming.data?.forEach((item) => {
              if (item) {
                merged[readField("_id", item) as string] = item;
              }
            });

            console.log("MERGED", merged);
            return merged;
          },
          read(existing) {
            return existing && Object.values(existing);
          },
        },
      },
    },
    Kit: {
      fields: {
        products: {
          keyArgs: false,
          merge(
            existing = [] as Product[],
            incoming: GetProductsResponse,
            { readField }
          ) {
            const merged = { ...existing };
            incoming.data?.forEach((item) => {
              if (item) {
                merged[readField("_id", item) as string] = item;
              }
            });
            console.log(merged);
            return merged;
          },
          read(existing) {
            return existing && Object.values(existing);
          },
        },
      },
    }
});

On the initial render of my react component, it loads the list of X number products. I use the fetchMore function, shown below, to request X more products from a specified cursor.

  const handleLoadMore = () => {
    if (kitProducts && kitProducts?.length > 0) {
      const remaining =
        getKitProductsData?.getKits.data[0]?.products.stats?.remaining;

      if (remaining && remaining > 0) {
        const cursor =
          kitProducts[kitProducts.length - 1] &&
          kitProducts[kitProducts.length - 1]?.createdAt;

        fetchMore({
          variables: {
            getProductsInput: {
              config: {
                pagination: {
                  reverse: true,
                  createdAt: cursor,
                },
              },
            },
          },
        });
      }
    }
  };

The products network request is fine, the kits do not duplicate in the cache, and the newly fetched (fetchMore) products are added to the “Kit” in the cache.


Thanks for the help, I have been stuck here for about 2 days - I really appreciate any advice ya’ll have!

I found out that the typePolicies merge function must return exactly the same type as the originating response for the react component to re-render with the updated data - If anyone is interested, I took the opportunity to share my findings in an article - Nested Pagination with Apollo Client 3