Search code examples
javascriptvue.jscheckboxpropv-model

Vue JS CheckBoxGroup with a prop array as v-model


I am stuck at making a CheckBoxGroup with a prop array as v-model. I have read the vuejs guide: https://v2.vuejs.org/v2/guide/forms.html#Checkbox which has the v-model array in the data of the same component, but it is obviously pretty useless if I want to make this component reusable and insert the v-model via props and for example check some of the boxes from "outside". So I tried following:

CheckBoxgroup.vue

<template>
  <div>
    <label v-for="day in allDays" :key="day">
      <input v-model="checkedDays" type="checkbox" :value="day" />
      <span>{{ day }}</span>
    </label>
    <div>Checked days: {{ checkedDays }}</div>
 </div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'

@Component
export default class CheckBoxGroup extends Vue {
  @Prop() checkedDays!: string[]

  @Prop() allDays!: string[]
}
</script>

Index.vue

<template>
  <div>
    <checkbox-group :checked-days="checkedDays" :all-days="allDays" />
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import CheckboxGroup from './checkBoxGroup.vue'

@Component({
  components: { CheckboxGroup },
})
export default class Index extends Vue {

  // This list would usually come from an API
  allDays = ['Monday', 'Tuesday', 'Wednesday']

  checkedDays = ['Monday']
}
</script>

So the code is working almost fine, but I am getting

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders...

Is there any way around it? Any help would be appriciated.


Solution

  • you can't mutate the parent state from the children directly, however you can emit the event from child to parent to mutate from there as below:

    Vue.component('check-box-group', {
      template: `
        <div>
          <label v-for="day in allDays" :key="day">
            <input 
              v-model="checkedDays" 
              :value="day" 
              @click="$emit('update-checked-days', { newCheckedDay: day })"
              type="checkbox" 
            />
            <span>{{ day }}</span>
          </label>
          <div>Checked days: {{ checkedDays }}</div>
        </div>
      `,
      props: {
        checkedDays: {
          type: Array, default: () => ([])
        },
        allDays: {
          type: Array, default: () => ([])
        },
      }
    })
    
    new Vue({
      el: "#app",
      data() {
        return {
          allDays: ['Monday', 'Tuesday', 'Wednesday'],
    
          checkedDays: ['Monday']
        }
      },
      methods: {
        HandleUpdateCheckedDays({newCheckedDay}) {
          const indexOfCheckedDay = this.checkedDays.findIndex(checkedDay => checkedDay === newCheckedDay)
    
          if (indexOfCheckedDay === -1) { // if not exists then add to checkedDays
            this.checkedDays.push(newCheckedDay)
          } else {
            this.checkedDays = this.checkedDays.filter((_, i) => i !== indexOfCheckedDay)
          }
        }
      }
    })
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
    
    <div id="app">
      <check-box-group 
        :checked-days="checkedDays" 
        :all-days="allDays" 
        @update-checked-days="HandleUpdateCheckedDays"
      />
    </div>

    note: remember that TS class composition is deprecated.