Search code examples
reactjsapollo-clientreact-apolloapollo-cache-inmemory

Apollo client v3 "Cache data may be lost when replacing the field X of a Y object"


I have two simple queries:

query GerMenuA {
  menus {
    menuA {
      items {
        label
      }
    }
  }
}

query GerMenuB {
  menus {
    menuB {
      items {
        label
      }
    }
  }
}

But in the console I see a warning:

Cache data may be lost when replacing the menus field of a Query object.
existing: {"__typename":"Menu","menuA":[{...}]}
incoming: {"__typename":"Menu","menuB":[{...}]}

Is there a way just do not merge them and remove the warning? Because if specify in typePolicies

Menu: { merge: true }
or
Menu: { merge: false }

it is not what I want, because these are different data and these two queues do not need to be merged in any way. Also, I don't have an id field and keyFields will not work for this case, because labels could be the same for both menus


Solution

  • So your question is hard to answer without your schema typedefs. But let's suppose it's something like this:

    type Query {
      menus: Menus
    }
    type Menus {
      menuA: Menu
      menuB: Menu
    }
    

    The warning is essentially saying that without any keyArgs Apollo Cache has no way to normalize the menus Query. Keep in mind from their standpoint- you have a single menus root query. (i.e. GerMenuA and GerMenuB are client side queries not root queries).

    (Side note- since you don't have id fields see disabling normalization.)

    Option 1: Separate Your Queries

    type Query {
      menuA: Menu
      menuB: Menu
    }
    

    Apollo cache will now store menuA and menuB as separate queries. If you want to be safe you can set your type policies:

    const createCache = () => new InMemoryCache({
      typePolicies: {
        Menu: {
          keyFields: false
        },
        Query: {
          fields: {
            menuA: {
              keyArgs: false
            },
            menuB: {
              keyArgs: false
            }
          }
        }
      },
    });
    

    keyFields: false tells AC to store Menu under it's parent query. keyArgs: false says that both menuA and menuB are singleton queries. Cache policy will default to merge: false so that existing data will be replaced by incoming.

    Option 2: Define Query Parameter

    In this option, you add a name parameter to your menus query:

    type Query {
      menus(name: String): Menu
    }
    

    By default, the cache stores a separate value for every unique combination of argument values you provide when querying a particular field.

    Apollo cache will now store menus:{name:menuA} and menus:{name:menuB} as separate cache objects. Again, if you want to be safe you can set your type policies:

    const createCache = () => new InMemoryCache({
      typePolicies: {
        Menu: {
          keyFields: false
        }
      },
    });
    

    Again, the cache policy will default to merge: false so that existing data will be replaced by incoming.

    Option 3: Define Merge Policy

    We've covered why the existing schema is confusing to Apollo Cache. But if you're really set on it, the remaining thing to do is define a merge policy:

    const createCache = () => new InMemoryCache({
      typePolicies: {
        Menu: {
          keyFields: false
        },
        Menus: {
          keyFields: false,
          merge: true
        },
        Query: {
          fields: {
            menus: {
              keyArgs: false,
              merge: true
            }
          }
        }
      },
    });
    

    merge: true tells Apollo Cache to merge the results of GerMenuA and GerMenuB into a single Menus cache object with both menuA and menuB properties. Without it each time you ran a query you'd blow away the results of the previous query.