Does `subscribeToMore` handle potential duplication?

I have two components, NotificationsLabel and NotificationsIcon. I have a query for each component, which fetches a list of notifications:

query NotificationsLabelSearchRecommendations {
  searchRecommendations {
    _id
    recommendationType
  }
}

query NotificationsIconSearchRecommendations {
  searchRecommendations {
    _id
    recommendationType
  }
}

I also have a subscription for each component:

subscription NotificationsLabelRecommendationChanged($tenantId: String!) {
  recommendationChanged(tenantId: $tenantId) {
    _id
    recommendationType
    subscriptionPubSubType
  }
}

subscription NotificationsIconRecommendationChanged($tenantId: String!) {
  recommendationChanged(tenantId: $tenantId) {
    _id
    recommendationType
    subscriptionPubSubType
  }
}

Each component calls their respective query with useQuery and calls subscribeToMore to listen for updates. In practice, this fetches an initial list of notifications and then updates the list when a notification comes in.

However, when a subscription is retrieved by these components, I see a duplication occur. Instead of having my list of notifications increment by +1, I see the same notification twice in each component.


My question is, do I need to check for duplicates in my updateQuery function? For example, I can do something like this:

const updateQuery = (prevData, { subscriptionData }) => {
  const newRecommendation = subscriptionData?.data?.recommendationChanged;
  if (!newRecommendation?._id) return prevData;

  const prevRecommendations = prevData?.searchRecommendations ?? [];
  const hasNewRecommendation = prevRecommendations.some(
    ({ _id }) => _id === newRecommendation._id
  );
  if (hasNewRecommendation) return prevData;

  return Object.assign({}, prevData, {
    searchRecommendations: [...prevRecommendations, newRecommendation],
  });

}

But I’m not sure if this is required, or if there’s another way Apollo Client should be handling this.

If it is required, how do I give consideration to performance if I am adding a subscription to large list?

Hey @jordanthornquest :wave:

I believe what you’re seeing is the result of each of those subscriptions writing to the cache, so you end up with duplicates. The value returned from updateQuery will write to the cache and broadcast those changes to active queries. Since both of your subscriptions are writing, they both broadcast to each query, and you end up with duplicates. I’d be willing to bet that you see those duplicates show up in the cache as well (you can use Apollo Client Devtools to inspect this, or console.log(client.extract()) which logs the cache contents). On the request side, I’m willing to bet you’ll only see one operation make it through your link chain due to queryDeduplication, even through you’re calling subscribeToMore twice (in case you’re wondering why that might be happening).

That said, I’d recommend trying to lift that query up higher in your component tree if you can and share that to your two components. If that is not feasible, I’d recommend at the very least only having one of those components run subscribeToMore, but not both. Let the cache update from that subscription propagate to your other component that way.

Hope this helps!

1 Like

Oh and I should mention, I’m assuming that you’re using the same $tenentId in both subscriptions. If that is not the case, let me know!

Hey Jerel,

This is immensely helpful and confirms what I was unsure of. You’re correct that the two queries use the same tenantId. Sounds like the best plan is to use a subscription in a higher-level component to update the query data with updateQuery, which would trigger re-renders for other components using the same query data. Is that correct?

In the same spirit of working with the cache and avoiding duplication, is there a best practice for working with subscriptions and optimistic updates? I’ve done some research and found the following discussions:

It seems like there isn’t an elegant way to do the following flow:

  1. A user action triggers a mutation (i.e. they add an item to a list)
  2. The item is optimistically added to the cache, which triggers an immediate UI update
  3. The server responds with a subscription event, which is used to update the cache and triggers another UI update

One StackOverflow discussion recommended checking for duplicate entries in the prevData object in both mutation and subscription functions, but that can get slow with large datasets. Would a better approach be to have mutations use the existing optimistic update flow and have the server filter/ignore sending subscription events that are triggered by the current user?

Thanks for your time :v: