1

The below given code you can see the magic of v-model, after checking / unchecking the checkboxes the array checkedNames will add/remove names automatically. We don't have to write anything to push/slice/filter names from the array, right?

const { ref } = Vue;

const App = {
  setup () {
    const checkedNames = ref([])
    return { checkedNames }
  }
}

Vue.createApp(App).mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <label for="mike">Mike</label>
  <br>
  <span>Checked names: {{ checkedNames }}</span>
</div>

My question is: How can I do the same feature but with custom components?

You can see, In below given code snippets I tried but I did not like it because we are missing that magic of v-model instead I am handeling this in my function addOrRemoveItem()

heads-up: It may seem to you that I am over explaining :) sorry for that.

I followed this tutorial for some idea but was not much helpful, then referred vue official doc here. below is the functional code but it's kinda overkill.

const { ref, createApp } = Vue;

const app = createApp({
  setup() {
    const itemsSelected = ref([]);
    const items = ref([
      { id: '1', name: 'Jack' }, 
      { id: '2', name: 'John' }, 
      { id: '3', name: 'Mike' }, 
    ]);

    const addOrRemoveItem = (itemId) => {
      const exists = itemsSelected.value.includes(itemId);

      if (exists) {
        itemsSelected.value = itemsSelected.value.filter((id) => id !== itemId);
      } else {
        itemsSelected.value.push(itemId);
      }
    };

    return { 
      items,
      itemsSelected,
      addOrRemoveItem,
    };
  },
});

app.component('custom-checkbox', {
   props: {
    item: { type: Object, default: () => ({}) },
    modelValue: { type: Array, default: () => [] },
  },
  template: `
    <div>
      <input
        type="checkbox" :value="item.id"
        @change="$emit('update:model-value', $event.target.checked)"
      > {{ item.name }}
    </div>
    `
})

app.mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <div><code>itemsSelected: {{ itemsSelected }}</code></div>
 <hr />
  <custom-checkbox
    v-for="item in items"
    :key="item.id"
    :item="item"
    :model-value="itemsSelected"
    @update:model-value="addOrRemoveItem(item.id)"
  ></custom-checkbox>
</div>

As mentioned above, the code was bit overkill when I follow as mentioned in doc $emit('update:model-value', ...) which can be simply anything for eg: $emit('val-updated'), here is bit simplified version after removing unwanted prop and reducing the length of $emit.

const { ref, createApp } = Vue;

const app = createApp({
  setup() {
    const itemsSelected = ref([]);
    const items = ref([
      { id: '1', name: 'Jack' }, 
      { id: '2', name: 'John' }, 
      { id: '3', name: 'Mike' }, 
    ]);

    const addOrRemoveItem = (itemId) => {
      const exists = itemsSelected.value.includes(itemId);

      if (exists) {
        itemsSelected.value = itemsSelected.value.filter((id) => id !== itemId);
      } else {
        itemsSelected.value.push(itemId);
      }
    };

    return { 
      items,
      itemsSelected,
      addOrRemoveItem,
    };
  },
});

app.component('custom-checkbox', {
   props: {
    item: { type: Object, default: () => ({}) },
  },
  template: `
    <div>
      <input
        type="checkbox" :value="item.id"
        @change="$emit('val-updated')"
      > {{ item.name }}
    </div>
    `
})

app.mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <div><code>itemsSelected: {{ itemsSelected }}</code></div>
 <hr />
  <custom-checkbox
    v-for="item in items"
    :key="item.id"
    :item="item"
    @val-updated="addOrRemoveItem(item.id)"
  ></custom-checkbox>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
Syed
  • 15,657
  • 13
  • 120
  • 154
  • I recommend to read this tutorial. The idea of using v-model is awesome. The result was also awesome. https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components The code will also be reduced(we don't need a function to handle the values in child component) – scar-2018 May 26 '21 at 11:16
  • @leonheess no, it doesn't help me bcoz here it's doing the same. Your suggested link have an answer from which my question starts :) – Syed May 26 '21 at 13:55
  • @Syed If it doesn't help you, why did you vote to close the question as a duplicate of yours/this? – leonheess May 26 '21 at 14:48
  • @leonheess good point, I marked it to close because Answer to my question by Michal Levý is correct way of handeling the reactiveness of the custom component. – Syed May 26 '21 at 14:52
  • @leonheess I believe closing this question was a mistake. Not only the answer in linked question is sub-optimal but it will only work in Vue 2 while this question was asked specifically for Vue 3. This should be reopened... – Michal Levý May 28 '21 at 16:36
  • 1
    @MichalLevý But the second and third won't ping anyone, as they didn't make any comments. – Adrian Mole May 29 '21 at 19:25
  • @AdrianMole OK, I get it. Deleted. Is there any way I can object to the decision and communicate with the voters? As it is clear theirs decision was in rush and without considering the tags. I'v already used the `flag` but it was "declined - flags should only be used to make moderators aware of content that requires their intervention" – Michal Levý May 29 '21 at 20:28
  • @MichalLevý Well, the reopen vote (presumably yours) will have put the question into the "Reopen" review queue, where others will have their chance to reopen the question, if they feel that way inclined. It has already received two "Leave Closed" votes, though, and one more such will (IIRC) take it out of that queue. – Adrian Mole May 29 '21 at 20:32
  • @MichalLevý In other similar situations, you can post a "Reopen Request" in the [SOCVR Chatroom](https://chat.stackoverflow.com/rooms/41570/so-close-vote-reviewers); but, as you have answered this question, you are "too involved" and such a request will be rejected from the room (against the rules). You *could* post on Meta.SE, if you feel strongly enough. – Adrian Mole May 29 '21 at 20:35
  • ^ That should be Meta.SO not Meta.SE. – Adrian Mole May 29 '21 at 20:47
  • 1
    @MichalLevý I was alerted to this question because the asker tried to close the other question as a duplicate of this one. If he himself has decided that those two questions are duplicates of each other - who am I to object? I just flipped the duplicate because the other question is way more popular and also older. If you can explain any difference that goes beyond Vue 2 vs Vue 3 to me, I will gladly vote to reopen myself. If not, I suggest you move your answer to the other question. – leonheess May 30 '21 at 08:16

1 Answers1

2

The addOrRemoveItem() method is not needed at all. Just let the default Vue v-model logic for a checkbox do the heavy lifting. Only thing you need is to use computed prop for a v-model (because using prop directly is not possible as props can not be mutated from child component)

const {
  ref,
  createApp
} = Vue;

const app = createApp({
  setup() {
    const itemsSelected = ref([]);
    const items = ref([{
        id: '1',
        name: 'Jack'
      },
      {
        id: '2',
        name: 'John'
      },
      {
        id: '3',
        name: 'Mike'
      },
    ]);

    return {
      items,
      itemsSelected,
    };
  },
});

app.component('custom-checkbox', {
  props: {
    item: {
      type: Object,
      default: () => ({})
    },
    modelValue: {
      type: Array,
      required: true
    }
  },
  emits: ['update:modelValue'],
  computed: {
    model: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  },
  template: `
    <div>
      <input
        type="checkbox" :value="item.id" v-model="model"
      > {{ item.name }}
    </div>
    `
})

app.mount("#app");
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>

<div id="app">
  <div><code>itemsSelected: {{ itemsSelected }}</code></div>
  <hr />
  <custom-checkbox v-for="item in items" :key="item.id" :item="item" v-model="itemsSelected"></custom-checkbox>
</div>
Michal Levý
  • 33,064
  • 4
  • 68
  • 86