Graphql - Apollo Client/ React - Cache - fetchMore doesn't update the data state of the query

Viewed 219

I try to implement cached pagination, in my react app using apollo client.

my query has filter argument, which should be the only argument that create a new key in the cache object.

for some reason, when fetchMore occurs with filter specified, the new data doesn't cause a re-render in the component.

I logged the existing and incoming argument in the merge function, and it seems that for each fetchMore that had filter, new data did arrive. so, i don't understand why the component didn't re-render.

to make things worst: calling fetchMore several times with or without filter send http request and merging the incoming data with the existing data. which i'd expect wouldn't happen as the client should see that it already has a key in the cache for that query with that key argument.

the following is the query:

query Shells($first: Int = 5, $offset: Int = 0, $filter: ShellFilter) {
  shells(
    orderBy: [STATUS_ASC, EXECUTION_FROM_DESC]
    first: $first
    offset: $offset
    filter: $filter
  ) {
    nodes {
      ...ShellData
    }
    totalCount
  }
}

the apolloClient config is like this:

const client = new ApolloClient({
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          shells: {
            keyArgs: ['filter'],
            merge: (existing, incoming) => {
              console.log('existing:', existing, 'incoming:', incoming);
              return mergeObjectsAndNestedArrays<ShellsConnection>(
                existing,
                incoming,
                'nodes',
              );
            },
          },
        },
      },
    },
  })

and the component that displays it:

const ControlCenter = () => {
  const { showModal } = useModalContext();
  const [page, setPage] = useState(1);
  const { data, loading, fetchMore } = useShellsQuery();
  const [query, setQuery] = useURLQuery();

  const onCounterpartiesChange = async (counterparties) => {
    await fetchMore({
      variables: {
        filter: { shellCounterParties: { some: { orgId: { in: '20584' } } } },
      },
    });
    setQuery({ counterparties });
  };

  const shells = data?.shells?.nodes;

  console.log('hello from shells:', shells);

these are the logs:

enter image description here

EDIT 1 - docs reference

Following the docs: https://www.apollographql.com/docs/react/pagination/key-args/#setting-keyargs

any argument can be used as the keyArgs: limit, offset and filter.

3 Answers

I think the problem lies where you have defined typePolicies in your code with keyArgs: ['filter'].

Please check official docs:

https://www.apollographql.com/docs/react/caching/cache-configuration/#customizing-cache-ids

You can customize how the InMemoryCache generates cache IDs for individual types in your schema

This is helpful especially if a type uses a field (or fields!) besides id or _id as its unique identifier.

Based on this, you have defined filter as a unique identifier even though that is a variable which is used for filtration purpose. It is not a field to customize the cache but a variable.

Note that these keyFields strings always refer to the actual field names as defined in your schema, meaning the ID computation is not sensitive to field aliases.

My suggestion first of all would be to modify the configuration that you have set up and see if it helps?

In the documentation examples, the arg used as the key is a primitive value, but in your case, the filter arg is an object. This could be causing apollo to see all results as the same cached version. If your data depend only on the orgID I think you could try the nested array notation to set that field as the key.

keyArgs: ["filter", ["shellCounterParties", ["some", ["orgId", ["in"]]]]]

or the custom function

keyArgs: (args, context) => args.filter.shellCounterParties.some.orgId.in

If you really need to cache according to the whole filter object, I guess the simplest way would be stringifying it

keyArgs: (args, context) => JSON.stringify(args.filter)

But to be sure how apollo is caching the data, I highly recommend you to try the apollo devtools

related: https://github.com/apollographql/apollo-client/issues/7314

Instead of fetchMore use refetch inside useEffect and pass there new variables

function photo({ id }) {
  const { data, refetch } = useQuery(GET_PHOTO, {
    variables: { id },
  });
  
  useEffect(() => {
    refetch({ id })
  }, [id])
}
Related