Subscription docs: why not subscribeForMore in parent component?

In the docs for Subscriptions, the example for subscribeForMore shows a CommentsPageWithData passing a callback (which will actually call subscribeForMore) into a child component, CommentsPage, which calls the callback in a useEffect hook. Why is that? What would be wrong with moving the useEffect call up into CommentsPageWithData?

E.g.

function CommentsPageWithData({ params }: CommentsPageWithDataProps) {
  const { subscribeToMore, ...result } = useQuery(
    COMMENTS_QUERY,
    { variables: { postID: params.postID } }
  );
  useEffect(() => {
    subscribeToMore({
      document: COMMENTS_SUBSCRIPTION,
      variables: { postID: params.postID },
      updateQuery: (prev, { subscriptionData }) => {
        if (!subscriptionData.data) return prev;
        const newFeedItem = subscriptionData.data.commentAdded;

        return Object.assign({}, prev, {
          post: {
            comments: [newFeedItem, ...prev.post.comments]
          }
        });
      });
    });
  });

  return (
    <CommentsPage {...result} />
  );
}

More on the design of the Apollo Client library, I’m also curious why useQuery couldn’t take a subscribeForMore-like option directly, rather than requiring it to be called back? It seems like the current design is made to be analagous to fetchMore, but that would usually be initiated by a user action, whereas I would think subscribeForMore would often want to be initiated immediately after the query completes, with access to the result of the query.

E.g.

function CommentsPageWithData({ params }: CommentsPageWithDataProps) {
  const { subscribeToMore, ...result } = useQuery(
    COMMENTS_QUERY,
    { 
      variables: { postID: params.postID },
      subscribeToMore((data) => ({
        document: COMMENTS_SUBSCRIPTION,
        variables: { postID: params.postID },
        updateQuery: (prev, { subscriptionData }) => {
          if (!subscriptionData.data) return prev;
          const newFeedItem = subscriptionData.data.commentAdded;

          return Object.assign({}, prev, {
            post: {
              comments: [newFeedItem, ...prev.post.comments]
            }
          });
        })),
      }),
    },
  );

  return (
    <CommentsPage {...result} />
  );
}

Hey @acjay :wave:

To be totally honest, I’m not sure why that subscribeToMore example is shown the way it is in the docs. I think its more to demonstrate that it can be passed around, but not necessarily prescribing exactly how to use it. That said, those docs pre-date my time on the team so I’m not sure the original motivation on why it was written that way.

That said, totally fine to use it with useEffect. Just make sure your useEffect includes a proper dependency array or you’ll end up creating new subscriptions every time your components render. subscribeToMore also returns a cleanup function that terminates the subscription so make sure this is returned from your useEffect callback so that the subscription terminates when the effect dependencies change or when the component unmounts.

useEffect(() => {
  return subscribeToMore({
    document: COMMENTS_SUBSCRIPTION,
    variables: { postID: params.postID },
    updateQuery: (prev, { subscriptionData }) => {
      if (!subscriptionData.data) return prev;
      const newFeedItem = subscriptionData.data.commentAdded;

      return Object.assign({}, prev, {
        post: {
          comments: [newFeedItem, ...prev.post.comments]
        }
      });
    });
  });
}, [params.postId]);

I’m also curious why useQuery couldn’t take a subscribeForMore -like option directly

We want to give you more control over when the subscription starts and giving you a function to call does that. For example, you might want to start the subscription only when the data returned by useQuery is in a certain state. Or perhaps you want to limit the subscription to a specific user-interaction. All of these are possible with the function-approach and very difficult with the callback-style approach.

The other issue with the callback-style approach is timing. When exactly should that subscription kick off? Immediately? After the initial query succeeds? What happens if you change variables by calling the refetch function?

All of these questions become increasingly difficult and I bet you’d find 10 different conflicting reasons on when becomes the “right” time to initiate the subscription. Not only that, but this approach would likely increase the API surface area quite a bit (its not unreasonable to think this approach would need a skip/disabled option for example; something that isn’t needed with the function approach since you just avoid calling it in that case)

Hope this helps!