'Passing v-model into a checkbox inside a Component in Vue 3?

I want to embed a checkbox inside a Vue3 Component and have the v-model binding passed down to the checkbox.

Inside the Component:

<!-- Tile.vue -->
<template>
  <div>
    <input type=checkbox v-model="$attrs">
  </div>
</template>
<script>
export default {inheritAttrs: false}
</script>

Then in an outside file:

<template>
<Tile value="carrot" v-model="foods" />
<Tile value="tomatoes" v-model="foods" />
</template>
<script setup>
var foods = ref([]);
</script>

How do I achieve this?

The documentation says that v-model is just a shorthand for :modelValue and @update:modelValue but this is not universal as Vue obviously behaves differently for form elements such as smartly listening to onchange instead of oninput and modifying the property checked instead of value depending on the node.

If I use v-model on the outer component, how do I forward it to the checkbox and get the same smart behavior that Vue has?



Solution 1:[1]

I have found tons of controversial information. Some recommend using @input event (Vue 3 custom checkbox component with v-model and array of items). Some recommend emitting modelValue:update instead of update:modelValue (https://github.com/vuejs/core/issues/2667#issuecomment-732886315). Etc.. Following worked for me after hour of trial and error on latest Vuejs3

Child

<template>
  <div class="form-check noselect">
    <input class="form-check-input" type="checkbox" :id="id" :checked="modelValue" @change="$emit('update:modelValue', $event.target.checked)" />
    <label class="form-check-label" :for="id"><slot /></label>
  </div>
</template>

<script>
import { v4 as uuidv4 } from "uuid";

export default {
  inheritAttrs: false,
  emits: ["update:modelValue"],
  props: {
    modelValue: {
      type: Boolean,
      required: true,
    },
  },
  setup() {
    return {
      id: uuidv4(),
    };
  },
};
</script>

Parent:

<Checkbox v-model="someVariable">Is true?</Checkbox>

you can verify that it works but doing this in parent:

    var someVariable= ref(false);

    watch(someVariable, () => {
      console.log(someVariable.value);
    });

p.s. The other solution above does not work for me. Author recommends using value property. But in example he passes v-model attribute. So I don't know exactly how it's supposed to work.

Solution 2:[2]

You can achieve the behavior by using emits to keep data in sync and behave as default v-model behavior. Checkbox component:

<template>
  <div>
    <input
      type="checkbox"
      :checked="value"
      @change="$emit('input', $event.target.checked)"
    />
    {{ text }}
  </div>
</template>

<script>
export default {
  name: "inputcheckbox",
  props: ["value", "text"],
};
</script>

And in the parent component you can have as many checkboxes you want.

<template>
  <div id="app">
    <maincontent :showContent="showContent" />
    <inputcheckbox text="one" v-model="checkedOne" />
    <inputcheckbox text="two" v-model="checkedTwo" />
  </div>
</template>

Here is a vue 2 example but is applicable to vue 3 as well. Hope this was helpful. Sandbox with this behavior:

https://codesandbox.io/embed/confident-buck-kith5?fontsize=14&hidenavigation=1&theme=dark

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2