Using Apollo, how can I distinguish newly created

2019-07-25 23:48发布

问题:

My use case is the following:

I have a list of comments that I fetch using a GraphQL query. When the user writes a new comment, it gets submitted using a GraphQL mutation. Then I'm using updateQueries to append the new comment to the list.

In the UI, I want to highlight the newly created comments. I tried to add a property isNew: true on the new comment in mutationResult, but Apollo removes the property before saving it to the store (I assume that's because the isNew field isn't requested in the gql query).

Is there any way to achieve this?

回答1:

Depends on what do you mean by "newly created objects". If it is authentication based application with users that can login, you can compare the create_date of comment with some last_online date of user. If the user is not forced to create an account, you can store such an information in local storage or cookies (when he/she last time visited the website).


On the other hand, if you think about real-time update of comments list, I would recommend you take a look at graphql-subscriptions with use of websockets. It provides you with reactivity in your user interface with use of pub-sub mechanism. Simple use case - whenever new comment is added to a post, every user/viewer is notified about that, the comment can be appended to the comments list and highlighted in a way you want it.

In order to achieve this, you could create a subscription called newCommentAdded, which client would subscribe to and every time a new comment is being created, the server side of the application would notify (publish) about that.

Simple implementation of such a case could look like that

const Subscription = new GraphQLObjectType({
    name: 'Subscription',
    fields: {
        newCommentAdded: {
            type: Comment, // this would be your GraphQLObject type for Comment
            resolve: (root, args, context) => {
                return root.comment;
            }
        }
    }
});


// then create graphql schema with use of above defined subscription
const graphQLSchema = new GraphQLSchema({
    query: Query, // your query object
    mutation: Mutation, // your mutation object
    subscription: Subscription
});

The above part is only the graphql-js part, however it is necessary to create a SubscriptionManager which uses the PubSub mechanism.

import { SubscriptionManager, PubSub } from 'graphql-subscriptions';

const pubSub = new PubSub();

const subscriptionManagerOptions = {
    schema: graphQLSchema,
    setupFunctions: {
        newCommentAdded: (options, args) => {
            newCommentAdded: {
                 filter: ( payload ) => {
                     // return true -> means that  the subscrition will be published to the client side in every single case you call the 'publish' method
                     // here you can provide some conditions when to publish the result, like IDs of currently logged in user to whom you would publish the newly created comment
                     return true;
            }
        }
    },
    pubsub: pubSub
});

const subscriptionManager = new SubscriptionManager(subscriptionManagerOptions);

export { subscriptionManager, pubSub };

And the final step is to publish newly created comment to the client side when it is necessary, via above created SubscriptionManager instance. You could do that in the mutation method creating new comment, or wherever you need

// here newComment is your comment instance
subscriptionManager.publish( 'newCommentAdded', { comment: newComment } );

In order to make the pub-sub mechanism with use of websockets, it is necessary to create such a server alongside your main server. You can use the subscriptions-transport-ws module.

The biggest advantage of such a solution is that it provides reactivity in your application (real-time changes applied to comments list below post etc.). I hope that this might be a good choice for your use case.



回答2:

I could see this being done a couple of ways. You are right that Apollo will strip the isNew value because it is not a part of your schema and is not listed in the queries selection set. I like to separate the concerns of the server data that is managed by apollo and the front-end application state that lends itself to using redux/flux or even more simply by managing it in your component's state.

Apollo gives you the option to supply your own redux store. You can allow apollo to manage its data fetching logic and then manage your own front-end state alongside it. Here is a write up discussing how you can do this: http://dev.apollodata.com/react/redux.html.

If you are using React, you might be able to use component lifecycle hooks to detect when new comments appear. This might be a bit of a hack but you could use componentWillReceiveProps to compare the new list of comments with the old list of comments, identify which are new, store that in the component state, and then invalidate them after a period of time using setTimeout.

componentWillReceiveProps(newProps) {

  // Compute a diff.
  const oldCommentIds = new Set(this.props.data.allComments.map(comment => comment.id));
  const nextCommentIds = new Set(newProps.data.allComments.map(comment => comment.id));
  const newCommentIds = new Set(
    [...nextCommentIds].filter(commentId => !oldCommentIds.has(commentId))
  );
  this.setState({
    newCommentIds
  });

  // invalidate after 1 second
  const that = this;
  setTimeout(() => {
    that.setState({
      newCommentIds: new Set()
    })
  }, 1000);
}

// Then somewhere in your render function have something like this.
render() {
  ...
  {
    this.props.data.allComments.map(comment => {
      const isNew = this.state.newCommentIds.has(comment.id);
      return <CommentComponent isNew={isNew} comment={comment} />
    })
  }
  ...
}

The code above was right off the cuff so you might need to play around a bit. Hope this helps :)