Search code examples
vue.jsvuejs3vue-component

two-way binding of a checkbox does not happen


For the code posted below vue component it is a child component and is hosted in a specific parent. When I make two ways binding via vModelCheckGeoTIFFOverlayBandAverages by introducing a change to compPropsVModelCheckGeoTIFFOverlayBandAverages, the changes do not get reflected in the parent component.

Am I making the binding of the checkbox wrong

code:

<template>
    <cds-checkbox id="idCheckboxGeoTIFFOverlayBandAveragesContainer">
        <label id="idTextGeoTIFFOverlayBandAverages">{{ tempTextBandAverages }}</label>
        <input
            type="checkbox"
            :value="compPropsVModelCheckGeoTIFFOverlayBandAverages"
            @input="$emit('update:compPropsVModelCheckGeoTIFFOverlayBandAverages', $event.target.checked)"
            :disabled="isCheckboxGeoTIFFOverlayBandAveragesDisabled"
            :checked="compPropsVModelCheckGeoTIFFOverlayBandAverages"
            @change="onCheckboxGeoTIFFOverlayBandAveragesChanged"
        />
    </cds-checkbox>
</template>

<script>
import { GeoTIFFOverlaySettingsConstants } from '../../../resources/....';
import { StoreOnCheckboxBandAveragesStatusChanged } from '../../../../../...';

let msg = null;

export default {
    name: 'SentinelHubGeoTIFFOverlayBand1',
    data() {
        return {
            refStoreOnCheckboxBandAveragesStatusChanged: StoreOnCheckboxBandAveragesStatusChanged,
            isCheckboxChecked: false,
            tempTextBandAverages: GeoTIFFOverlaySettingsConstants.CONST_STRING_BAND_1_AVERAGES.description,
        };
    },
    components: {},
    props: {
        isCheckboxGeoTIFFOverlayBandAveragesDisabled: {
            type: Boolean,
            default: true,
        },
        vModelCheckGeoTIFFOverlayBandAverages: {
            type: Boolean,
            default: null,
        },
    },
    emits: {
        'update:vModelCheckGeoTIFFOverlayBandAverages':null,
    },
    computed: {
        compPropsVModelCheckGeoTIFFOverlayBandAverages: {
            get() {
                return this.vModelCheckGeoTIFFOverlayBandAverages;
            },
            set(value) {
                this.$emit('update:vModelCheckGeoTIFFOverlayBandAverages', value);
            },
        },
    },
    watch: {
        isCheckboxChecked(newVal, oldVal) {
            this.compPropsVModelCheckGeoTIFFOverlayBandAverages = newVal;
        },
    },
    methods: {
        async onCheckboxGeoTIFFOverlayBandAveragesChanged() {
            this.isCheckboxChecked = !this.isCheckboxChecked;
            await this.refStoreOnCheckboxBandAveragesStatusChanged.actions.init();
            this.refStoreOnCheckboxBandAveragesStatusChanged.setters.set(this.isCheckboxChecked);
        },
    },
};
</script>

explained above in the question


Solution

  • An input emits update:compPropsVModelCheckGeoTIFFOverlayBandAverages event, while the name of the event is update:vModelCheckGeoTIFFOverlayBandAverages. This shows the problem with long naming. v-model already suggests the convention, "modelValue" prop and "update:modelValue" event. As long as a component deals with one value, these names are unambiguous and can be conventionally used.

    It's still beneficial to use it with v-model with native elements like input instead of direct use of props and events because this allows to skip low-level details like $event.target.checked and make code shorter and less error-prone.

    It's unclear if the component requires to have its own local state. If it's not needed, there's no need for data() for component state, and the names can be kept short and intelligible:

    <label id="idTextGeoTIFFOverlayBandAverages">{{ labelText }}</label>
    <input
      type="checkbox"
      v-model="isChecked"
      :disabled="disabled"
    />
    

    and

    props: {
        modelValue: {
            type: Boolean,
            default: false,
        },
        disabled: {
            type: Boolean,
            default: false,
        },
    },
    emits: {
        'update:modelValue':null,
    },
    data() {
        return {
            labelText: GeoTIFFOverlaySettingsConstants.CONST_STRING_BAND_1_AVERAGES.description,
        };
    },
    computed: {
        isChecked: {
            get() {
                return this.modelValue;
            },
            set(value) {
                this.$emit('update:modelValue', value);
            },
        },
    },
    watch: {
        isChecked(newVal, oldVal) {
          // can be used to do anything on value updates, e.g. sync a store
        },
    }
    

    And is supposed to be used as:

    Or without v-model, the usage is more straightforward than with input:

    <SentinelHubGeoTIFFOverlayBand1
      :modelValue="someValue"
      @update:modelValue="updateSomeValue"
      :disabled="someDisabled"/>