0

I have a data array, and each item in the array has a state property that contains one of these values, allowed, pending, rejected. I want to display data in separate sections based on their state. Each data card has two buttons on it that can be used to change item state.

I'm using three computed properties to separate data like so:

computed: {
    pendingData() {
        return this.data.filter(x => x.state === 'pending')
    },
    allowedData() {
        return this.data.filter(x => x.state === 'allowed')
    },
    rejectedData() {
        return this.data.filter(x => x.state === 'rejected')
    }
}

and I display them in their own section using v-for.

Changing state occurs through an API call, so I need to send the id of item and new state to setState function:

<div class="state-change-buttons">
    <button @click="setState(item.id, 'allowed')">
        <span uk-icon="icon: check; ratio: 1.4"></span>
    </button>
    <button @click="setState(item.id, 'pending')">
        <span uk-icon="icon: future; ratio: 1.4"></span>
    </button>
</div>

and this is setState function:

setState(id, state) {
    const index = this.data.findIndex(x => x.id === id)
    this.$axios.post(`/api/${id}`, {state})
        .then(res => {
            this.data.splice(index, 1, res.data)
        })
        .catch(err => {
            this.$notify(err, 'danger')
        })
}

As you can see, to update my data array in realtime I have to find the index of selected item using findIndex.

Vue style guide suggests not to use v-if on the same element as v-for. But should I still avoid it, even if it decreases my app, time complexity?

current scenario:
3 computed property (O(3n)) + findIndex (O(n)) + 3 v-for (O(p + r + q))

conditional v-for:
3 conditional v-for (O(3n)) (no findIndex, this way I can pass index to setState directly)

conditional v-for code:

<div v-for="(item, index) in data" v-if="item.state === 'pending'" :key="item.id">
    <!-- data card body (including buttons) -->
</div>

<div v-for="(item, index) in data" v-if="item.state === 'allowed'" :key="item.id">
    <!-- data card body (including buttons) -->
</div>

<div v-for="(item, index) in data" v-if="item.state === 'rejected'" :key="item.id">
    <!-- data card body (including buttons) -->
</div>
4
  • I'm not quite sure how the v-if and v-for fit in your template, can you add that? Because from what I'm seeing and understanding, they shouldn't be on the same root element either way. Commented Jul 17, 2019 at 20:02
  • @StevenB. I added the code at the end of my post. Commented Jul 17, 2019 at 20:12
  • Possible duplicate of Correct way to handle v-if with v-for in Vue Commented Jul 17, 2019 at 20:33
  • Using v-if with v-for is not recommended vuejs.org/v2/style-guide/#Avoid-v-if-with-v-for-essential Commented Sep 5, 2020 at 12:39

2 Answers 2

0

It's unclear whether v-if is related to the parent or the child. You can use a template to resolve this issue:

<template v-for="(item, index) in data">
  <div v-if="item.state === 'rejected'" :key="item.id">
    <!-- data card body (including buttons) -->
  </div>
</template>
Sign up to request clarification or add additional context in comments.

1 Comment

It's the opposite, v-for has higher priority.
0

As the comment to your question says I can't understand either your computation without seeing the 2 proposed templates. Nevertheless, this is my personal preference when facing this kind of lists.

Have the data as an object. This way finding the value will be O(1). and will increase readability. Also have the array as a computed property.

export default {
  name: 'list',
  data: () => ({
    data: {
      'id1': {id: 'id1', state: 'pending'},
      'id2': {id: 'id2', state: 'allowed'},
      'id3': {id: 'id3', state: 'rejected'},
    },
  }),
  computed: {
    dataList() {
      return Object.values(this.data)
    },
    pendingData() {
        return this.dataList.filter(x => x.state === 'pending')
    },
    allowedData() {
        return this.dataList.filter(x => x.state === 'allowed')
    },
    rejectedData() {
        return this.dataList.filter(x => x.state === 'rejected')
    },
  },
  methods: {
    setState(id, state) {
    this.$axios.post(`/api/${id}`, {state})
      .then(res => {
          Object.assign(this.data[id], res.data);
      })
      .catch(err => {
          this.$notify(err, 'danger')
      })
    }
  }
};

Then you just use in the template like so:

<div v-for="item of pendingData" :key="item.id">
    <!-- data card body (including buttons) -->
</div>

<div v-for="item of  allowedData" :key="item.id">
    <!-- data card body (including buttons) -->
</div>

<div v-for="item of rejectedData" :key="item.id">
    <!-- data card body (including buttons) -->
</div>

NOTE: Code changed after author's comment to my response. so this way you avoid v-if because you are already iterating over different lists.

1 Comment

There's no such thing as filter. All three lists are rendered simultaneously. The only difference in the template is that v-if is added to v-fors, so computed properties and findIndex are omitted.

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.