1

I have a list a discussion object containing an array of comments and each comment can hold an array of replies. I display the discussion this way:

<div v-for="comment in comments" v-bind:key="comment._id">
  <Comment :itemId="itemId" :comment="comment" />
  <Replies v-if="comment.replies.length > 0" :itemId="itemId" :comment="comment" />
</div>
<Button value="Load more" @clicked="loadMoreComments(itemId)" />

and Replies:

<div v-for="reply in replies" v-bind:key="reply._id">
  <Comment :itemId="itemId" :comment="reply" />
</div>
<Button :value="Load more" @clicked="loadChild()"/>

As you can see both use the same pattern. They differ in a computed property:

computed: {
  comments() {
    return this.$store.getters.DISCUSSION.comments.map(id => this.$store.getters.GET_COMMENT(id));
  },
  replies() {
    return this.$store.getters.GET_REPLIES(this.comment).map(id => this.$store.getters.GET_COMMENT(id));
  },
},

When I hit the Load more button for comments, new comments appear. But when I hit the Load more button in replies, then no new reply is displayed though I can see in debugger that the array was enlarged.

Vuex store submodule:

state: () => ({
  discussion: {
    incomplete: true,
    comments: [],
  },
  comments: {},
}),

getters: {
  DISCUSSION: state => state.discussion,
  GET_COMMENT: state => id => state.comments[id],
  GET_REPLIES: state => (comment) => {
    if (comment.allShown) {
      return comment.replies;
    }
    return comment.replies.slice(0, REPLY_LIMIT);
  },
},
mutations: {
  APPEND_COMMENTS: (state, payload) => {
    const { comments, incomplete, userId } = payload;
    state.discussion.incomplete = incomplete;
    const commentIds = [];
    comments.forEach(comment => processComment(state, comment, commentIds, userId));
    state.discussion.comments = state.discussion.comments.concat(commentIds);
  },
  PREPEND_COMMENTS: (state, payload) => {
    const { comments, userId } = payload;
    const commentIds = [];
    comments.forEach(comment => processComment(state, comment, commentIds, userId));
    state.discussion.comments = commentIds.concat(state.discussion.comments);
  },
  SET_REPLIES: (state, payload) => {
    console.log('SET_REPLIES');
    const { commentId, replies, userId, replace } = payload;
    const comment = state.comments[commentId];
    if (!comment) {
      return;
    }

    state.comments[commentId].showAll = true;
    const commentIds = [];
    replies.forEach(reply => processComment(state, reply, commentIds, userId));
    if (!comment.replies || comment.replies.length === 0 || replace) {
      state.comments[commentId].replies = commentIds;
    } else {
      state.comments[commentId].replies = comment.replies.concat(commentIds);
    }
  },
}

function processComment(state, comment, commentIds, userId) {
  if (comment.replies) {
    const repliesIds = [];
    comment.replies.forEach((reply) => {
      reply.voted = hasVoted(reply.votes, userId);
      state.comments[reply._id] = reply;
      repliesIds.push(reply._id);
    });
    comment.replies = repliesIds;
    comment.allShown = comment.replies.length < REPLY_LIMIT;
  } else if (!comment.parentId) {
    comment.replies = [];
    comment.allShown = false;
  }
  state.comments[comment._id] = comment;
  commentIds.push(comment._id);
}

The complete source code is there: https://github.com/literakl/mezinamiridici/tree/comment_refactoring/spa

Here is minimum reproducible codesandbox: https://codesandbox.io/s/frosty-taussig-v8u4b?file=/src/module.js

I have verified that this happens because of the getter with a parameter. When I put the reply in static array so I could use parameter-less getter, it started to work.

I follow this recommendation: https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143

Where is the issue?

Update:

One thing that smells is the mutation GET_REPLIES because it works on the passed object. So Vue has no chance to detect that the object is from the state. So I have rewritten it to pass only commentId and load the comment from the state, but it did not help.

1 Answer 1

1

I guest you should replace showAll with allShown prop and also use Vue.set where you add new keys to comments object because due to Vue caveats Vuex doesn't see new props, see caveats for objects

    SET_REPLIES: (state, payload) => {
      console.log("SET_REPLIES");
      const { commentId, replies, userId, replace } = payload;
      const comment = state.comments[commentId];
      if (!comment) {
        console.log(`Comment ${commentId} not found`);
        return;
      }

      state.comments[commentId].allShown = true;
      // state.comments[commentId].showAll = true;

...

function processComment(state, comment, commentIds, userId) {
  if (comment.replies) {
    const repliesIds = [];
    comment.replies.forEach(reply => {
      Vue.set(state.comments, reply._id, reply);
      // state.comments[reply._id] = reply;
      repliesIds.push(reply._id);
    });
    comment.replies = repliesIds;
    comment.allShown = comment.replies.length < 3;
  } else if (!comment.parentId) {
    comment.replies = [];
    comment.allShown = false;
  }
  Vue.set(state.comments, comment._id, comment);
  // state.comments[comment._id] = comment;
  commentIds.push(comment._id);
}

Also correct GET_REPLIES call like this:

  computed: {
    replies() {
      return this.$store.getters
        .GET_REPLIES(this.comment) // passing comment itself instead of its id
        .map(id => this.$store.getters.GET_COMMENT(id));
    }
  },

corrected example

Sign up to request clarification or add additional context in comments.

3 Comments

Hello, I found that typo as well, but it did not help. Your sandbox is not working, "Cannot read property 'slice' of undefined"".
just correct GET_REPLIES call as I mentioned at the end of my answer
Vue.set(state.comments, comment._id, comment); is the game changer, thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.