apollo-client icon indicating copy to clipboard operation
apollo-client copied to clipboard

Cannot modify @client field with cache.modify

Open scaraux opened this issue 2 years ago • 12 comments

Intended outcome:

Calling cache.modify should modify the @client` field in the cache

Actual outcome:

The cache.modify call returns false, the field is not updated in the cache

How to reproduce the issue:

This is what I have:

export const GET_CREATORS = gql`
  query GetCreators($limit: Limit) {
    creators(limit: $limit) {
      edges {
        node {
          id
          name
          profileImageUrl
          isFollowing @client
        }
      }
    }
  }
`;
const typeDefs = gql`
  extend type Creator {
    isFollowing: Boolean!
  }
`;
   new ApolloClient({
        ...
        cache: new InMemoryCache({
          typePolicies: {
            Creator: {
              fields: {
                isFollowing: {
                  read(_, { cache, readField}) {
                    const creatorId: string | undefined = readField('id');
                    if (!creatorId) return false;
                    const cachedFollows: GetMyCreatorFollows | null = cache.readQuery({
                      query: MY_CREATOR_FOLLOWS,
                    });
                    return cachedFollows?.myCreatorFollows.includes(creatorId) ?? false;
                  },
                },
              },
            },
           ...

In my mutation's update I do:

          const res = cache.modify({ 
            id: cache.identify({
              __typename: 'Creator',
              id: creatorId,
            }),
            fields: {
              isFollowing() {
                return true;
              },
            },
          });

The cache modify call fails. It only works if the field is not a @client defined field.

Versions

  System:
    OS: macOS 12.3
  Binaries:
    Node: 16.14.2 - ~/.nvm/versions/node/v16.14.2/bin/node
    Yarn: 1.22.18 - ~/ntwrk/ntwrk-mobile/node_modules/.bin/yarn
    npm: 8.5.0 - ~/.nvm/versions/node/v16.14.2/bin/npm
  Browsers:
    Chrome: 102.0.5005.61
    Safari: 15.4
  npmPackages:
    @apollo/client: 3.4.17 => 3.4.17
    apollo: ^2.34.0 => 2.34.0

scaraux avatar Jun 06 '22 21:06 scaraux

I suspect the immediate issue here is that cache.modify currently only works for fields that explicitly exist in the cache (so, for example, it can't add new fields). Since isFollowing is a "virtual" field with only a read function, cache.modify effectively doesn't see it. I think I agree this is a bug, so I'm not defending this behavior, just describing it.

This seems fixable, because we know the cache.modify call is attempting to modify isFollowing, and the InMemoryCache knows that there's a custom read function defined for the Creator.isFollowing field. Even calling the cache.modify callback with an undefined value would work for your code. I think that might be the right answer, in fact, as opposed to calling the read function to obtain an existing value, since cache.modify is intended to operate on the internal cache data, pre-read, so it might be strange to feed it the result of calling read.

However, you may want to think about where you expect that true return value to be stored, since your read function doesn't currently do anything with the existing _ value. The easiest answer (I think) is to modify your read function so it returns the existing value (first parameter) if defined, and only falls back to the MY_CREATOR_FOLLOWS subquery if existing is undefined. That way, your cache.modify code should work, by explicitly setting the internal isFollowing field value to true.

benjamn avatar Jun 07 '22 17:06 benjamn

@benjamn You are correct, this is what's happening. However, the first parameter is always undefined. I thought that returning its default value would implicitly store it in the cache. I had to modify my read so it returns the cached value if defined, otherwise reads it and explicitly saves it. Is that the expected behavior?

scaraux avatar Jun 07 '22 19:06 scaraux

Is there any workaround for this? I was hoping to manage a simple boolean flag using a local-only field. According to the docs that should be possible without typePolicies because I just want to read from the cache directly, but I’m not sure if I still need to define a default of false somewhere. I couldn’t get that approach to work with any of cache.modify, cache.writeQuery or cache.updateFragment though 😞

kinglozzer avatar Jan 31 '23 16:01 kinglozzer

Running into a similar issue with updateFragment. After the first updateFragment call, subsequent calls to updateFragment act as if previous calls never happened, even though the response from the updateFragment shows the data has been updated. My cache is persisted with MMKV.

pfcodes avatar Feb 08 '23 08:02 pfcodes

Same issue here! @benjamn any updates about this? Any way to update a local field without reactive variables?

Nasseratic avatar Feb 23 '23 16:02 Nasseratic

No one is looking into this? Same issue for me

amitozalvo avatar May 02 '23 14:05 amitozalvo

Same issue. It would be very useful for us ...

takacskalman avatar Aug 17 '23 20:08 takacskalman

In our case, we need to initialize the client fields with some values then to make updates by cache.modify, but I see that it's not working like this. The modify works only with non @client fields

takacskalman avatar Aug 17 '23 20:08 takacskalman

Facing the same issue

chrahman avatar Nov 18 '23 23:11 chrahman

Same issue here. If intentionally not supported would have useful to note this in the docs, spent a bunch of time trying to figure out where I was going wrong

DavidBanksNZ avatar Dec 12 '23 05:12 DavidBanksNZ

Would like to see this fixed.

pirtlj avatar Feb 26 '24 23:02 pirtlj

We are facing the same issue, has anyone been able to find a workaround for this?

VasRoko avatar May 14 '24 10:05 VasRoko