1

I am building a Q&A website much like stackoverflow, where you can vote a question or answer. To simplify I kept the code for voting up.

This is the vote component is used on both question and answer components.

<template>
  <div class="vote">
    <h4 @click="voteUp">
      <i :class="{'fa fa-chevron-circle-up' : votedUp>-1, 'fa fa-chevron-up': votedUp==-1 }"></i>
    </h4>
  </div>
</template>

<script>
  export default {
    props: ['votes','item'],
    computed:{
      votedUp() {
        return _.findIndex(this.votes, {user_id: this.$root.authuser.id});
      }
    },
    methods:{
      voteUp() {
        axios.post(this.$root.baseUrl+'/vote_item', {item_id:this.item.id,item_model:this.item.model,vote:'up'})
             .then(response => {
                _.isEmpty(response.data) ? this.votes.splice(this.votedUp,1) : this.votes.push(response.data);
              })
      }
    }
  }
</script>

This is the question component that uses vote component:

<template>
  <div class="question">
    <h1>{{ question.title }}</h1>
    <div class="row">
      <div class="col-xs-1">
        <vote :votes="question.votes" :item="item"></vote>
      </div>
      <div class="col-xs-11 pb-15">
        <p>{{ question.body }}</p>
        <comment-list :comments="question.comments" :item="item" ></comment-list>
      </div>
    </div>
    <answer v-for="answer in question.answers" :answer="answer"></answer>
  </div>
</template>

<script>
  export default {
    props: ['question'],
    data(){
      return {
        item:{ id:this.question.id, model:'Question' }
      };
    }
  }
</script>

This is the answer component that uses vote component:

<template>
  <div class="answer row">
    <div class="col-xs-1 mt-20">
      <vote :votes="answer.votes" :item="item"></vote>
    </div>
    <div class="col-xs-11">
      <h3>{{ answer.title }}</h3>
      <p>{{ answer.body }}</p>
      <comment-list :comments="answer.comments" :item="item" ></comment-list>
    </div>
  </div>
</template>

<script>
  export default {
    props: ['answer'],
    data(){
      return {
        item:{ id:this.answer.id, model:'Answer' }
      };
    }
  }
</script>

THE ISSUE

Voting up works fine and changes the state of both question.votes and answer.votes, but it only renders the HTML of answers. I have to refresh to see the upvote on question. Also in Vue developper tool console, answer.votes get refreshed automatically while I need to hit the vue refresh button to see question.votes taking the new vote into account (but still no HTML rendering).

IMPORTANT NOTE

As you can see, you can also comment on question and answer, and this is working fine on both because I used a different approach to $emit an event. Maybe this is a solution to my answer, but what I really want to know is why the functionnality in vote is working on answer and not on question. Thanks!

3
  • Aren't using Vue in development mode? You shouldn't be changing/mutating props. Like in vote component - this.votes.push. Commented Feb 22, 2017 at 10:56
  • Yes I am using Vue in dev mode. How do you suggest I do it instead? Commented Feb 22, 2017 at 19:09
  • Then you should have a warn from Vue in console. Commented Feb 22, 2017 at 19:23

1 Answer 1

1

Your vote component should not mutate props. Mutate a local copy:

export default {
  props: ['votes','item'],
  data() {
    return { votesCopy: [] };
  },
  mounted() {
    this.votesCopy = this.votes.slice();
  },
  computed:{
    votedUp() {
      return _.findIndex(this.votesCopy, {user_id: this.$root.authuser.id});
    }
  },
  methods:{
    voteUp() {
      axios.post(this.$root.baseUrl+'/vote_item', {item_id:this.item.id,item_model:this.item.model,vote:'up'})
           .then(response => {
              _.isEmpty(response.data)
                ? this.votesCopy.splice(this.votedUp,1)
                : this.votesCopy.push(response.data);
            })
    }
  }
}
Sign up to request clarification or add additional context in comments.

6 Comments

It works, thanks. But I still don't understand why the component should not mutate props. Can you please explain? Why would I copy and burden memory when it's more efficient to change the prop?
I guess this discussion explains better than I can.
It seems that the correct way to keep everything in sync is emitting events to change the parent's state. Your solution works and is more code-friendly (all voting logic stays in vote component) but creates an unsynced state between parent and child states. Thanks for sharing.
One last question: why did you slice votes in mounted() ? I tried it with data(){ return { votesCopy: this.votes; } } and it works
Well, ok then. It's my way to create an array copy.
|

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.