简体   繁体   中英

ApolloClient fetchMore with custom merge updates cache, but useQuery returns old data

I'm trying to implement pagination according to the ApolloClient core pagination guide: https://www.apollographql.com/docs/react/pagination/core-api

This is my type policy:

typePolicies: {
  Query: {
    fields: {
      paginatedProductTracking: {
        // Include everything except 'skip' and 'take' to be able to use `fetchMore`
        //  and repaginate when reading cache
        //  (essential for switching between desktop pagination and mobile lazy loading
        //   without having to refetch)
        keyArgs: (args) => JSON.stringify(omit(args, ['query.skip', 'query.take'])),
        merge: (existing = [], incomingResponse, { args }) => {
          const responseData = incomingResponse?.paginatedData || [];
          return [
            // conservative merge that handles if pages are not requested in order
            ...existing.slice(0, args?.query.skip || 0),
            ...responseData,
            ...existing.slice((args?.query.skip || 0) + responseData.length),
          ];
        },
      },
    },
  },
},

As you see in the comment, one complication is that skip and take are in a nested arg called query , but it looks fine in the cache.

This is my components render function (leaving things out that should be irrelevant for this issue, but let me know if something is missing:

...
const initialQuery = {
  skip: 0,
  take: 3,
  ...
}

const { data, loading, fetchMore, networkStatus } = useProductTrackingAggregatedDataQuery({
  notifyOnNetworkStatusChange: true,
  variables: {
    query: initalQuery,
});

...

return <InfinityScrollList
  // Apollo merges values for `variables`, but only in a shallow way
  // Hence merging the query params manually
  onLoad={async () => {
    await fetchMore({
      variables: {
        query: {
          ...initialQuery,
          skip: items.length,
        },
      },
    });
  }}
/>

I feel like I'm doing the right thing, because the Apollo Cache looks as expected and it does update when I fetch more entries:

initial cache初始缓存

after fetchMore在此处输入图像描述

I can also see the expected network request.

The problem is that my component doesn't rerender:/

I forced rerendering by adding networkStatus to my query result, but I didn't get the merged result form the cache either (but the inital list). Doing this, I also noticed that I didn't receive the network status 3 (fetchMore), but I only see 1 (loading) and then 7 (standby).

Using the lazy hook could be a workaround, but I'd really like to avoid that because I'm trying to set an good example in the code base and it would ignore cache invalidation.

It might be relevant that my data doesn't have an id: 在此处输入图像描述

I'm on the latest ApolloClient version ( 3.7.1 ).

Providing a minimal working example for this would be tough, unfortunately.

ok, I found a solution and it's kind of an interesting things that I don't really see in the official documentation. I was trying to write just the array content ( paginatedData ) in the cache and thought as long as I do that consistently I'm good:

        merge: (existing = [], incomingResponse, { args }) => {
          const responseData = incomingResponse?.paginatedData || [];
          return [
            // conservative merge that handles if pages are not requested in order
            ...existing.slice(0, args?.query.skip || 0),
            ...responseData,
            ...existing.slice((args?.query.skip || 0) + responseData.length),
          ];
        },

And I did see the result of my custom merge function in the dev tools, which sounds like a bug. Because the query hook still returned the unmerged data, which is nested in an object (I didn't include this in the question):

  const items = data?.paginatedProductTracking.paginatedData || [];

So it makes sense that this doesn't work, because the data I wrote in the cache did not conform with the data returned from the API. But I was tripped by the dev tools suggesting it's working.

I solved it by writing the data in the cache with the same type and structure as the as the API response:

        typePolicies: {
          Query: {
            fields: {
              paginatedProductTracking: {
                // Include everything except 'skip' and 'take' to be able to use `fetchMore`
                //  and repaginate when reading cache
                //  (essential for switching between desktop pagination and mobile lazy loading
                //   without having to refetch)
                keyArgs: (args) => JSON.stringify(omit(args, ['query.skip', 'query.take'])),
                merge: (existing, incoming, { args }) => {
                  if (!existing) {
                    return incoming;
                  }
                  if (!incoming) {
                    return existing;
                  }
                  const data = existing.paginatedData;
                  const newData = incoming.paginatedData;
                  return {
                    ...existing,
                    // conservative merge that is robust against pages being requested out of order
                    paginatedData: [
                      ...data.slice(0, args?.query.skip || 0),
                      ...newData,
                      ...data.slice((args?.query.skip || 0) + newData.length),
                    ],
                  };
                },
              },
            },
          },
        },

And that did the trick:)

fetchMore calls do not update the initial query's variables, all they do is fetch more data with a no-cache policy and write them to the cache with the implementation of your merge function for the given type policy. This is explained here

You will need to update the initial query's variables after the successful call to fetchMore to render the new data. Also the data will be read with the read function in your typePolicies for the given type or the entire cached result will be returned if none is provided. You should re-implement you pagination within the read function if returning the entire cached set is not the desired result (which would only be the case if your ui implements something like infinite scrolling).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM