0

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.

1 Answer 1

2

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

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

2 Comments

This is awesome! If you were going to expand this so that you could also work with v-model values that were booleans or strings would you check for those values in the watch() statement?
I'm not sure I understand your question, do you mean that the boolean or string would be in the passed instead of the array? I suppose regardless of the answer, I would still use a watch but the watch implementations may need to do more type checking.

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.