Brenda Jimenez
Brenda Jimenez

Reputation: 761

Subscription component with GraphQL Apollo Client

I have an application with one subscription already using subscribeToMore

Query Component:

<Query query={GET_MESSAGES}>
  {({ data, subscribeToMore }) => {
    return (
      <MessageList
        messages={data.allMessages}
        subscribeToMore={subscribeToMore}/>
    );
  }}
</Query>

That query loads a list of messages where as far as I know we attach the subscriber in the ComponentDidMount so whenever I add a new element into my list inside the Query, my subscription will listen to the subscription and do whatever I want (in this case add the new message into my current list of messages).

List of Messages Component:

export default class MessageList extends React.Component {
componentDidMount() {
  this.props.subscribeToMore({
    document: MESSAGE_CREATED,
    updateQuery: (prev, { subscriptionData }) => {
      if (!subscriptionData.data) return prev;
      return {
        allMessages: [
          subscriptionData.data.messageCreated,
          ...prev.allMessages
        ],
      };
    },
  });
}

render() {
  return (
    <div>
      {this.props.messages.map(message => (
        <MessageElement key={message.id} 
                        message={message}/>
      ))}
    </div>
  );
}}

I would like to add another subscription so if I edit my message the information is updated in real time. In order to achieve that I have created the following component using Subscription

Message component (where I would like to add another subscription based on an updated message)

export default class MessageElement extends Component{

componentDidMount() {
    this.messageUpdatedSubscription();
}


messageUpdatedSubscription = () => (
    <Subscription
      subscription={MESSAGE_UPDATED}>
      {({ data: { messageUpdated } }) => (
        console.log('Do smthg???',messageUpdated)
      )}
    </Subscription>
  );


render(){
    return(
        <Mutation mutation={UPDATE_MESSAGE} key={this.props.message.id}>
         {updateMessage => (
            <div>
                <div>{this.props.message.text}</div> 
                <div>
                    <Star active={this.props.message.isFavorite} 
                         onClick={() => { updateMessage({ variables: { 
                                          id: this.props.message.id, 
                                          text:this.props.message.text,
                                          isFavorite:!this.props.message.isFavorite } });
                    }}/>
                </div>
            </div>
          )}
       </Mutation>);
}}

My subscriptions on the server are working as I already have the subscription for MESSAGE_CREATED on the Query working correctly and I have tested that on the server my subscription for the MESSAGE_UPDATED is triggered. However, I cannot figure out why the UI is not displaying or console.log anything as if it is not listening to the subscription.

Can I achieve this with a subscription component or with a subscribeToMore?

Thanks in advance

Upvotes: 1

Views: 2004

Answers (3)

Adeel Rizvi
Adeel Rizvi

Reputation: 124

I saw a lot of users struggling to use useSubscription in Class Component as Apollo deprecated the Subscription component in the newer version. So, I decided to create quick gist of how to use it.

https://gist.github.com/syedadeel2/a9ec6ff0d9efade2b83d027f32ce21dc

Upvotes: 1

Brenda Jimenez
Brenda Jimenez

Reputation: 761

Based on the suggestion of @cory-mcaboy I nested my subscription into the mutation. I also found out that as I had a list of messages and I just wanted to trigger the subscription based on the message I am updating and not the entire list; I had to modified my subscription on the backend and on the front end in the following way:

Server schema

const typeDefs = gql` type Subscription {
                      messageUpdated(id: Int!): Message}`;

Server resolver

 Subscription: {
    messageUpdated: {
        subscribe: withFilter(
                   () => pubsub.asyncIterator([MESSAGE_UPDATED]),
                         (payload, variables) => {
                         return payload.messageUpdated.id === variables.id;
                                        },
                   ),
      },
}

Client component

const MESSAGE_UPDATED = gql` subscription messageUpdated($id: Int!){
            messageUpdated(id:$id)
            { 
                id
                text
                isFavorite
            }}`;

export default class MessageElement extends Component{
render(){
    return(<Mutation mutation={UPDATE_MESSAGE} key={this.props.message.id}>
            {updateMessage => (
                <div>
                    <div>{this.props.message.text}</div> 
                     <Subscription subscription={MESSAGE_UPDATED}
                                   variables={{ id: this.props.message.id }}>
                        {() => {
                            return <Star active={this.props.message.isFavorite} 
                                        onClick={() => { updateMessage({ variables: { 
                                                                        id: this.props.message.id, 
                                                                        text: this.props.message.text,
                                                                        isFavorite: !this.props.message.isFavorite } });}}/>
                        }}
                    </Subscription>
                </div>
            )}
        </Mutation>
    );
}}

You can see the code in the following repo: back-end itr-apollo-server, front-end itr-apollo-client

Upvotes: 1

Cory McAboy
Cory McAboy

Reputation: 332

The subscription component cannot be initiated in ComponentDidMount. It has to reside in a render() lifecycle method. It's parameter can be used in ComponentDidMount, but not the component.

I can think of 3 possible solutions:

1) You could try to put the Subscription method in your render method. You would just need to nest this inside or outside of your Mutation component.

2) You could initiate the Subscription Component in this component's parent and pass its parameter (messageUpdate) down to the component MessageElement. You could then use messageUpdate off of props in ComponentDidMount.

3) You could use Apollo's higher order component. You could then access messageUpdate in props. (Disclaimer - I have not tried this before).

I hope this helps!

Upvotes: 1

Related Questions