Search code examples
reactjsgraphqlreact-apolloapollo-clientgraphql-subscriptions

Apollo GraphQL - Associated types not included on Subscription response


Let's say I have a Post and Comment model. A Post hasMany Comments. In React Apollo I'm using subscribeToMore on a Query for a particular post.

The query appears as follows:

query getPost(id: ID!){
    id, title, comments { id }
}

And the subscription which returns the post with any new comments:

subscription commentAdded(postId: ID!){
    id, title, comments { id }
}

The query works. It returns all of the associated Comments, which I can then render as in list on the page.

Yet when using the subscribeToMore helper for the query, I get the follow error whenever the event subscription is dispatched.

Cannot read property 'Comment' of undefined.

The strange thing is that if I remove the Comment, such that the subscription looks like...

subscription commentAdded(postId: ID!){
    id, title
}

...it works perfectly. I'm confused why it seems to treat the Comments as associating with an undefined model.

This isn't just a Comments -> Posts issue, this happens on any model that tries to return a subscription with an association.

post query:

    post: async (parent, {id}, {models}) => {
        return await models.Post.findByPk(id);
    }

saveComment resolver:

    saveComment: async (parent, {postId, comment}, {models, me}) => {
        let post = await models.Post.findByPk(postId);
        let comment = await models.Comment.create({comment, postId});
        await pubsub.publish("COMMENT_CREATED", {
            commentCreated: post,
            postId
        })
    }

commentCreated subscription:

    commentCreated: {
        subscribe: withFilter(
            () => pubsub.asyncIterator(["COMMENT_CREATED"]),
            (payload, variables) => {
                return payload.postId == variables.postId
            }
        )
    }

Post type resolver

Post: {
    comments: async (post, args, {models}) => {
        return await models.Comment.findAll({where:{postId: post.id}});
    }
}

Server initialization:

const server = new ApolloServer({
    typeDefs: schema,
    resolvers,
    subscriptions: {
        onConnect: (connectionParams, webSocket) => {
            return true
        },
    },
    context: async ({ req, connection }) => {
        if(connection){
            return connection.context;
        }else{
            const me = await getMe(req);
            return {
                models,
                me,
                secret: process.env.SECRET,
            };
        }
    }
});

Solution

  • Your context function only returns connection.context, which will not include any of the custom properties you want to include (me, models, etc.). Doing something like this should fix the problem:

    context: async ({ req, connection }) => {
      const context = {
        models,
        secret: process.env.SECRET,
      };
    
      if(connection){
        return { ...connection.context, ...context };
      } else {
        const me = await getMe(req);
        return { ...context, me };
      }
    }