Home > Net >  What is the proper way to implement groups when creating a checkbox component in Vue 3?
What is the proper way to implement groups when creating a checkbox component in Vue 3?

Time:02-16

I'm creating a Checkbox component in Vue 3 that is using v-model to create a binding to an an array for a checkbox group. This group binding is an optional feature and in most cases the value of the checkbox will not populate an array and either be null or some string. I think I'm getting closer to a solution, but it seems rather complex especially considering that you can bind 3 inputs pretty simply like so: https://medium.com/dataseries/vue-3-v-model-c896566aed64

Here is my implementation in App.vue

<template>
  <checkbox modelValue="apple" v-model:group="group" />
  <checkbox modelValue="orange" v-model:group="group" />
  <checkbox modelValue="grape" v-model:group="group" />
  <p>{{ group }}</p>
</template>

<script setup>
  import { ref } from 'vue';
  import Checkbox from './components/Checkbox.vue'
  const group = ref(["apple"]); //"Apple" should start out checked, but it currently doesn't.
</script>

And here is Checkbox.vue

<template>
  <input 
  type="checkbox"
  :value="modelValue"
  @change="updateValue($event)"
  />
  <label for="checkbox">{{ modelValue }}</label>
</template>

<script setup>
  import { defineEmits, ref, onMounted } from 'vue';
  const checkbox = ref(null);
  const props = defineProps({
    modelValue: String,
    group: Array
  })

  const emit = defineEmits(['update:modelValue'])

  onMounted(()=>{
    //Some code to determine initial state depending on what props.group looks like?
  })
  function updateValue(e) {
    if (e.target.checked){
      props.group.push(e.target.value);
    }
    if(!e.target.checked){
      const toRemove = props.group.indexOf(e.target.value);
      props.group.splice(toRemove, 1);
    }
    emit('update:modelValue', props.group)
  }
  defineExpose({ checkbox})
</script>

Currently, the array populates and depopulates based one what gets checked and unchecked, but "apple", which is initially set up to be checked, currently isn't. It bothers me that the implementation with inputs can be so simple, but I'm finding myself writing a whole bunch of code to keep the two-way binding between the checkbox group and the group array. Before I go down the rabbit hole of writing a whole bunch of code in the onMounted hook, I have to wonder if there is a simpler way of doing all of this? Even the code I've written for updateValue() seems to be a bit too much.

CodePudding user response:

Not to say that the implementation pr path is wrong, but my inclination would be to use v-model and watch instead of event listeners. I believe this makes it simpler to handle handle things like duplicate values.

App.vue

<template>
  <checkbox value="apple" v-model="group" />
  <checkbox value="orange" v-model="group" />
  <checkbox value="grape" v-model="group" />
  <hr/><!-- duplicates -->
  <checkbox value="apple" v-model="group" />
  <checkbox value="grape" v-model="group" />
  <p>{{ group }}</p>
</template>

<script setup>
  import { ref } from 'vue';
  import Checkbox from './Checkbox.vue'
  const group = ref(["apple"]);
</script>

Checkbox.vue

<template>
  <label>
    <input 
      type="checkbox"
      :value="value"
      v-model="checked"
    />
    {{ value }}
  </label>
</template>

<script setup>
  import { watch, ref } from 'vue';
  const checkbox = ref(null);
  const props = defineProps({
    modelValue: Array,
    value: String
  });
  
  const checked = ref(props.modelValue.includes(props.value));
  
  watch(checked, (v) => {
    if(!v && props.modelValue.includes(props.value)) {
      const toRemove = props.modelValue.indexOf(props.value);
      props.modelValue.splice(toRemove, 1);
    } else if(v && !props.modelValue.includes(props.value)) {
      props.modelValue.push(props.value);
    }
  })
  
  watch(props.modelValue, (mv) => {
    if(checked.value && !mv.includes(props.value)) {
      checked.value = false;
    } else if(!checked.value && mv.includes(props.value)) {
      checked.value = true;
    }
  })
</script>

In the example I used a ref to store internal true/false value. Then there are two watches that handle the discrepancy between checked and modelValue (mapped to the group array).

link to SFC PLayground

  • Related