After discussion with the team, it looks like this might actually be a limitation of the way the cache is currently implemented. Lenz recently opened an issue very similar in nature here (No access to fields on old an new value in a typePolicy.merge function · Issue #11221 · apollographql/apollo-client · GitHub). This is definitely something we want to address in a future version of the client to make this easier.
For now, I have a couple suggestions to help you move forward. I don’t entirely know how you’ve set everything up, but hopefully this gives you some ideas to make this work well enough for your use case.
- Add a
merge
function to the root subscription onMessage
field.
merge
functions are called relative to their field in the operation you’re executing. Here because the data is returned from your root subscription operation, the data is written to the cache via the onMessage
field first. This might be an opportunity to intercept that object before its written to the cache, you just have to do it at a different point in the type policy than expected.
The caveat here is that we don’t cache root subscription fields by default, so to read the existing data in the cache, you might need to use something like cache.readFragment()
to get the data you need.
See if something like this works for you (warning untested):
new InMemoryCache({
typePolicies: {
Subscription: {
fields: {
onMessage: {
merge: (_, incoming, { cache }) => {
const existing = cache.readFragment({
id: cache.identify(incoming) // assuming `incoming` has __typename and id fields on it
fragment: gql`
fragment ChatRecord on Chat {
messageCount
# other fields you need access to
}
`
})
// do stuff with existing + incoming here
}
}
}
}
}
})
- Use a
ref
in the same component you have your useSubscription
call to keep track of the last message or set of messages.
If the above doesn’t work for you, or if you’re still having troubles, you might be able to keep track of the previous value yourself using a ref. Less-than-ideal but might still work for your needs.
function MyComponent() {
const lastMessageRef = useRef()
useSubcription(subscription, {
onData: ({ data }) => {
const prev = lastMessageRef.current;
// do something with prev + new data
lastMessageRef.current = data;
}
})
}
Hopefully one of these two options work for you!
We definitely realize this is a major shortcoming of the current API and not the first time we’ve seen this type of thing asked for. Again, this is something on our radar that we plan to address at some point in a major version where we can make some breaking changes to make this a bit more intuitive.