0

I have Posts and Comments and I'm trying to build the UI for adding a comment to a post using React and Meteor. When I submit a comment, the new comment appears and then immediately disappears. When I refresh the page, the new comment is there.

Meteor.publish('post', function(postId) {
  return Posts.find(postId);
});

Meteor.publish('comments.forPost', function(postId) {
  const post = Posts.findOne(postId);
  return Comments.find({_id: { $in : post.comments } } );
});

Meteor.methods({
  'comments.insert'({ postId, content }) {
    check(postId, String);
    check(content, String);

    const commentId = Comments.insert({
      createdAt: new Date(),
      userId: this.userId,
      content,
    });

    Posts.update(postId, { $addToSet: { comments: commentId }});
});

In my React component I use createContainer:

export default createContainer((props) => {
  const id = props.params.id;
  const postHandle = Meteor.subscribe('post', id);
  const isLoading = !postHandle.ready();
  Meteor.subscribe('comments.forPost', id);
  const post = Posts.findOne(id);
  const comments = 
    isLoading ? [] : Comments.find({_id: { $in: post.comments } }).fetch();
  console.log(comments.length);
  return {
    comments,
    isLoading,
    question,
  };
}, PostShow);

My console.log statement prints the new length after adding a comment, and then prints the previous number.

philpee2
  • 125
  • 1
  • 6

1 Answers1

0

That is because in your publication post is evaluated only once and therefore your cursor is not "reactive" in the sense that it will not re-evaluate the query when you comments change, as it is fetched.

There are several ways to do this, either directly, using a package or redesign to avoid it.

  1. Do it directly, as demonstrated in this video. I would not recommend this unless you really know what you are doing and have a very good reason to do so. It involves directly observing your first query and manually pushing updates to the client when something changes. I think that David Weldon summarizes it quite well in this answer.
  2. De-normalize the data, which will require you to keep track of the duplicates as the canonical data source changes. This moves the complexity to another area of the code, and is not very easy to implement. There are certain design patterns, such as CQRS, that allow you to achieve this in a fairly robust and performant way, but they are not easy to grasp and implement.
  3. Use a package, such as reywood:publish-composite, that makes it a little less painful that the manual option at the price of potential performance penalty.
  4. Change your data structure to allow easy publication. That depends on your use-case and the rest of the requirements. In your case, having an articleId in each comment document will make publishing all of the comments for each article trivial. You may want to add an index to that field to boost performance.

BTW, the "reactive join" problem is still unsolved and will likely remain so since a new GrapnQL-based data model is coming to Meteor via Apollo soon.

Community
  • 1
  • 1
MasterAM
  • 16,283
  • 6
  • 45
  • 66
  • Seems like option 4 is the best bet? Just to clarify, you mean instead of having an array of comment IDs in the post document, I should instead have a post ID in the comment document? I did that originally, since it made sense from a relational databse POV, but I saw a Mongo tutorial suggesting against doing that unless the number of comments per post was huge http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1 – philpee2 Jun 04 '16 at 17:20
  • I have seen this post in the past. There may be good reasons to do so in plain Mongo, but when you need to have reactivity, this might cause more problems that it is worth. I admit to not benchmarking both approaches myself. – MasterAM Jun 04 '16 at 17:27
  • Also, in that model, what's the best way to cascade delete all of a post's comments when the post is deleted? – philpee2 Jun 04 '16 at 17:28
  • There are multiple approaches, beginning with stuff like collection-hooks, to CQRS-like architectures such as Meteor-space or de-normalization automation tools like PeerDB. You can also do it in an allow/deny rule (I would not recommend it) or a method call that would simply delete the post along with all comments with the that `articleId`. – MasterAM Jun 04 '16 at 17:31
  • Thanks for the help! – philpee2 Jun 04 '16 at 18:13