apollo-client: [Discussion] fetchMore is a bad match with cache redirects (type policies)

As outlined in a previous comment (https://github.com/apollographql/apollo-client/pull/6350#issuecomment-636176607) cache redirects can be annoying because when you use cache.modify you will have to remember to update the fields which feed the type policy instead of the ones you’re actually working on.

Another thing worth mentioning in the docs is that fetchMore doesn’t also play well with cache redirects: it will write a new query to the cache with the new elements appended. Probably you want to keep both queries (the source one used in type policies and the target one) in sync with the latest changes, so you’ll have to remember to manually update the source one in the updateQuery method of fetchMore.

How to improve the current state of the things? In a previous comment (https://github.com/apollographql/apollo-client/pull/6289#issuecomment-631416262) I suggested that type policies seem half baked and that would be nice to be able to use them the other way around as well: instead of having to use the update method, whenever I insert a new item or remove an existing one I would love to be able to define a policy for that, which will be in charge to update the rest of the cache. That would resolve all of the previous issues and would also help to decouple the cache updating logic to the specific components (which shouldn’t have knowledge of the rest of the application).

Unfortunately I don’t know how to turn this into a GitHub Discussion.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 19 (8 by maintainers)

Most upvoted comments

@darkbasic I’ve been thinking more about options.args, and I believe I have a good workaround for you.

For any field that you plan to modify with cache.modify, if you need to examine the most recent arguments, you can store the arguments yourself using a read and merge function, and everything outside the cache will work just like it did before:

new InMemoryCache({
  typePolicies: {
    ParentType: {
      fields: {
        someField: {
          read(existing) {
            // Since the read function ignores existing.args and just returns existing.value,
            // the args are effectively hidden from normal code that reads from the cache.
            return existing?.value;
          },
          merge(existing, incoming, { args }) {
            return {
              // You could adjust/filter the args here, or combine them with existing.args,
              // or whatever makes sense for this field.
              args, 
              value: doTheMerge(existing?.value, incoming, args),
            };
          },
        },
      },
    },
  },
})

If this becomes a common pattern, you could wrap it up in a helper function:

new InMemoryCache({
  typePolicies: {
    ParentType: {
      fields: {
        someField: fieldWithArgs((existingValue, incomingValue, args) => ...),
      },
    },
  },
})

function fieldWithArgs<TValue>(
  doTheMerge: (
    existing: TValue,
    incoming: TValue,
    args: Record<string, any>,
  ) => TValue,
): FieldPolicy<{
  args: Record<string, any>;
  value: TValue;
}> {
  return {
    read(existing) {
      return existing?.value;
    },
    merge(existing, incoming, { args }) {
      return {
        args, 
        value: doTheMerge(existing?.value, incoming, args),
      };
    },
  };
}

Later, when you call cache.modify, the arguments you saved will be accessible in the modifier function:

cache.modify({
  id: cache.identify({ __typename: "ParentType", parentId }),
  fields: {
    someField({ args, value }) {
      // Examine args to help with processing value...
      return { args: newArgs, value: newValue };
    },
  },
})

I honestly think this workaround gives you more power to get things right than any automated options.args solution would. For example, you could maintain a historical list of all arguments objects received so far, and then cache.modify could look back in time, rather than relying on just the most recent args. I can’t think of a good use case for that idea, but that’s the kind of freedom that read and merge functions give you.

@benjamn I just had to do exactly that, I needed to access the args in order to update the cache. Returning them from the merge function works perfectly for that use case.

I’m going to work on some helper functions to generate field policies for pagination today, and I will make sure they cover these use cases.

As I mentioned over in https://github.com/apollographql/apollo-client/issues/5951#issuecomment-642313482, fetchMore is definitely broken right now, and fixing it will require some breaking changes, so I’m hoping to make the transition as easy as possible (hence the helpers).