0

I'm using GraphQL subscriptions for my chat application, but I have an issue with the UI updates.

Here are the query and the mutation I'm using:

const createMessage = gql`
    mutation createMessage($text: String!, $sentById: ID!) {
        createMessage(text: $text, sentById: $sentById) {
            id
            text
        }
    }
`

const allMessages = gql`
    query allMessages {
        allMessages {
            id
            text
            createdAt
            sentBy {
                name
                location {
                    latitude
                    longitude
                }
            }
        }
    }
`

Then, when exporting, I'm wrapping my Chat component like so:

export default graphql(createMessage, {name : 'createMessageMutation'})(
  graphql(allMessages, {name: 'allMessagesQuery'})(Chat)
)

I'm subscribing to the allMessagesQuery in componentDidMount:

componentDidMount() {

  // Subscribe to `CREATED`-mutations
  this.createMessageSubscription = this.props.allMessagesQuery.subscribeToMore({
    document: gql`
        subscription {
            Message(filter: {
                mutation_in: [CREATED]
            }) {
                node {
                    id
                    text
                    createdAt
                    sentBy {
                        name
                    }
                }
            }
        }
    `,
    updateQuery: (previousState, {subscriptionData}) => {
      console.log('Chat - received subscription: ', previousState, subscriptionData)
      const newMessage = subscriptionData.data.Message.node
      const messages = previousState.allMessages.concat([newMessage])
      console.log('Chat - new messages: ', messages.length, messages) // prints the correct array with the new message!!
      return {
        allMessages: messages
      }
    },
    onError: (err) => console.error(err),
  })

}

After I sent the message through the chat, the subscription is triggered successfully and I see the two logging statements that also contain the expected data. So the contents of messages are definitely correct within updateQuery!

However, the UI doesn't update automatically, in fact, all the previously displayed messages disappear.

My render method looks as follows:

render() {
  console.log('Chat - render: ', this.props.allMessagesQuery)
  return (
    <div className='Chat'>
      <ChatMessages
        messages={this.props.allMessagesQuery.allMessages || []}
      />
      <ChatInput
        message={this.state.message}
        onTextInput={(message) => this.setState({message})}
        onResetText={() => this.setState({message: ''})}
        onSend={this._onSend}
      />
    </div>
  )
}

The logging statement in render shows that initially, this.props.allMessagesQuery has the array allMessages, so everything works after the initial loading.

After the subscription is received, allMessages disappears from this.props.allMessagesQuery which is why an empty array is given to ChatMessages and nothing is rendered.

Before subscription is triggered

enter image description here

After subscription is triggered

enter image description here

nburk
  • 22,409
  • 18
  • 87
  • 132

2 Answers2

0

I figured it out after digging through the docs one more time, it was a mistake on my end!

This part from the Apollo docs helped me solve the issue:

Remember: You'll need to ensure that you select IDs in every query where you need the results to be normalized.

So, adding the id fields to the returned payload of my queries and subscriptions actually helped.

const allMessages = gql`
    query allMessages {
        allMessages {
            id
            text
            createdAt
            sentBy {
                id
                name
                location {
                    id
                    latitude
                    longitude
                }
            }
        }
    }
`

subscription {
    Message(filter: {
        mutation_in: [CREATED]
    }) {
         node {
            id
            text
            createdAt
            sentBy {
                id
                name
            }
        }
    }
}
nburk
  • 22,409
  • 18
  • 87
  • 132
-1

In your update Query the previousState is the allMessages query that is saved in the react apollo store.

To update the store you have to make a deep copy or use some helper e.g. reacts immutability helper. The apollo developers page gives some information about how to update the store correctly update Query.

In your case it could look like this

...
updateQuery: (previousState, { subscriptionData }) => {
  const newMessage = subscriptionData.data.Message.node;
  return update(previousState, {
    allMessages: { $push: [newMessage] },
  });
}
...
Locco0_0
  • 3,420
  • 5
  • 30
  • 42