Refreshing the v-model in a child component

Within my parent component, the code structure is similar to this:

<template>
    <ProductCounter v-model="formData.productCount" label="product count" />
</template>

<script setup>

const initialFormData = {
    productCount: null,
    firstname: '',
    surname: '',
    phone: '',
    email: '',
    postcode: '',
    submittedOnce: false,
    errors: []
}

let formData = reactive({ ...initialFormData });

const clearUI = () => {
    formData = reactive({ ...initialFormData });
    triggerInlineForm.value = false;
}

</script>

The child component is structured like this:

<template>
    <div class="form__row" @reset-counts="resetCount">
        <div class="counter__row">
            <label>{{ label }}</label>
            <div class="form__counter">
                <button class="form__button--decrease form__button--circle form__button--animate-scale" :disabled="value == 0 || props.disabled" @click.prevent="decreaseCount()">
                    <i>
                        <FontAwesomeIcon :icon="['fal', 'minus']" />
                    </i>
                </button>
                <input type="text" v-model="value" :disabled="props.disabled" @input="updateQty" placeholder="0"/>
                <button class="form__button--increase form__button--circle form__button--animate-scale" :disabled="props.disabled" @click.prevent="increaseCount()">
                    <i>
                        <FontAwesomeIcon :icon="['fal', 'plus']" />
                    </i>
                </button>
            </div>
        </div>
    </div>
</template>

<script setup>
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';

const emits = defineEmits(['update:modelValue', 'resetCounts']);

const props = defineProps({
    label: {
        type: String,
        required: true
    },
    modelValue: {
        type: String,
        required: true,
        default: 0
    },
    disabled: {
        type: Boolean,
        required: false
    }
});

const value = ref(props.modelValue);

const updateQty = () => {
    emits('update:modelValue', value.value)
}

const increaseCount = () => {
    value.value++
    emits('update:modelValue', value.value)
}

const decreaseCount = () => {
    value.value--;
    emits('update:modelValue', value.value)
}

</script>

When triggering clearUI from the parent, I anticipated that when formData is reset, the v-model of ProductCounter would return to 0. However, it does not. Where might I be making an error?

Answer №1

Link to the live solution

Next time, please provide a minimum reproducible example on https://play.vuejs.org/. Regarding your question:

In Vue, do not overwrite reactive variables...

Mutate them instead

(Object.assign(formData, initialFormData))
:

Also, avoid dereferencing component properties like this:

(const value = ref(props.modelValue))
. This can cause the properties to lose their reactivity due to copying a primitive value.

The optimal way to create a v-model pattern is by using computed, which allows direct manipulation in the template.

const value = computed({
  get(){
    return props.modelValue;
  },
  set(val){
    emits('update:modelValue', val);
  }
});

Additionally, ensure that your count property is defined as a number, not a string (to avoid Vue warnings):

modelValue: {
    type: Number,
    required: true,
    default: 0
},

Furthermore, there's no need to update the prop on the input event since you're already utilizing v-model. Also, make sure to convert your input's model to a number:

<input type="text" v-model.number="value" :disabled="props.disabled" placeholder="0"/>

Here's what you have:

App.vue

<template>
    <p>
      <ProductCounter v-model="formData.productCount" label="product count" />
    </p>
    <button @click="clearUI">
      Clear
    </button>
    <div>
      {{ JSON.stringify(formData) }}
  </div>
</template>

<script setup>
import ProductCounter from './ProductCounter.vue'
import {reactive} from 'vue'
  
const initialFormData = {
    productCount: 0,
    firstname: '',
    surname: '',
    phone: '',
    email: '',
    postcode: '',
    submittedOnce: false,
    errors: []
}

let formData = reactive({ ...initialFormData });

const clearUI = () => {
    Object.assign(formData, initialFormData);
}

</script>

ProductCounter.vue:

<template>
    <div class="form__row">
        <div class="counter__row">
            <label>{{ label }}</label>
            <div class="form__counter">
                <button :disabled="value == 0 || props.disabled" @click.prevent="value--">
                -
                </button>
                <input type="text" v-model.number="value" :disabled="props.disabled" placeholder="0"/>
                <button :disabled="props.disabled" @click.prevent="value++">
                 +
                </button>
            </div>
        </div>
    </div>
</template>

<script setup>
import {computed} from 'vue';
const emits = defineEmits(['update:modelValue']);

const props = defineProps({
    label: {
        type: String,
        required: true
    },
    modelValue: {
        type: Number,
        required: true,
        default: 0
    },
    disabled: {
        type: Boolean,
        required: false
    }
});

const value = computed({
  get(){
    return props.modelValue;
  },
  set(val){
    emits('update:modelValue', val);
  }
});


</script>

Answer №2

When you modify the formData in the clearUI() function, you are updating the variable's content:

let formData = reactive({ ...initialFormData });

const clearUI = () => {
    formData = reactive({ ...initialFormData });
}

However, this change does not affect the object that was originally linked to the template during component setup. To address this issue, you can utilize ref and assign a new value to it:

const formData = ref({ ...initialFormData });

const clearUI = () => {
    formData.value = { ...initialFormData };
}

Alternatively, you can update the properties individually:

const formData = reactive({ ...initialFormData });

const clearUI = () => {
    Object.assign(formData, initialFormData);
}

Another concern is that when setting the value within ProductCounter to the initial value of props.modelValue, it remains static since it is not a reactive property. To resolve this, you can add a watcher:

const value = ref(props.modelValue);
watch(
  () => props.modelValue,
  () => value.value = props.modelValue
)

This way, value will update accordingly when props.modelValue changes.

You can see this in action in a playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Caution: It is important for every child in a list to have a distinctive "key" prop value. How can a shared key be used for multiple items?

When I click on a header from my list, an image should appear. However, I'm encountering an error that needs to be resolved. I've been struggling to find a solution for adding unique keys to multiple elements in order to eliminate the error. Des ...

Dispatch is functioning properly, however the state remains unchanged

I'm currently developing an application that utilizes redux for state management. In a particular scenario, I need to update the state. Here is how my initial state and reducer function are set up: import { createSlice } from '@reduxjs/toolkit&a ...

Angular 8 experiencing unexpected collision issues

Currently, I am utilizing Angular 8 with "typescript": "~3.5.3". My objective is to handle the undefined collision in my code. const { testLocation } = this.ngr.getState(); this.step2 = testLocation && testLocation.step2 ? testLocat ...

The Node.js engine isn't updating to support compatibility with Firebase functions

Encountered First Failure Below is the content of package.json "engines": { "node": "8.0.0" }, Error: The engines field in the functions directory's package.json is unsupported. You can choose from: {&quo ...

What causes the appearance of an HTTP header error and how can we identify the bug?

I tried to convert XML to JSON using two files which are included here. However, I keep encountering an error. Despite searching on SO for solutions, I haven't been able to find the answers. main.js /** SET ENGINE TO PUG */ app.set("views", ...

Is it possible to combine multiple jQuery selectors for one command?

I need some help with jQuery commands. I have two similar commands, but not exactly the same. Here are the commands: $("#abc > .className1 > .className2 > .className3"); $("#pqr > .className1 > .className2 > .className3"); Is there a w ...

How can I track and log the name of the country each time it is selected by a click?

I'm currently utilizing VueJS along with a REST API and axios to retrieve the list of countries, then showcasing them in card format on the webpage. However, I am facing a challenge in creating a history list that captures the last 5 countries clicked ...

Troubleshooting: Issues with window.location.href and window.open within an iframe

Code Update <div> <button type="button" class="submit btn btn-default" id="btnSubmit">Submit </button> <button type="button">Cancel</button> </div> <script> $("#btnSubmit").click(function(e) { ...

Even with the v-once directive applied, the component will still update whenever there is a change in the value of a property within a

Why does a component with the v-once directive re-render when passing an object as a prop and changing its property value? What causes the internal Watcher of the component to trigger a re-render? I have observed that when passing a primitive as a prop, t ...

What is the process for editing a JSON file and preserving the modifications?

I have a JSON file with a key (food_image) and I am looking to dynamically assign the index value in a loop to it, such as food_image0, food_image1 ... food_image{loop index}. After that, I aim to save this modified JSON file under a new name. All values ...

When the mouse leaves, the gauge chart component's size will expand

I have encountered a problem while using the react-gauge-chart library in my react project. Within the project, I have integrated a popover component using material-ui and incorporated the gauge chart component from the library within the popover modal. T ...

Creating identical class names for UL elements underneath LI using JavaScript

In my attempt to dynamically generate the class name within the ul element, I successfully achieved the expected result. The outcome can be viewed in the console of the snippet provided for reference. However, I'm facing a challenge when attempting t ...

Extract the response data following a axios post request (using vue and laravel)

My goal was to devise a unique login system utilizing vue for the frontend, laravel for handling the API, and axios as the intermediary between the two. The challenge I faced was retrieving the data returned from my AuthController. Below are my code snippe ...

verifying the presence of offspring in knockout js

Utilizing knockout for displaying items on the page, I have a series of groups such as Group 1, Group 2, etc. Each group is contained within its own div. Upon clicking on a group, it expands to showcase the items within that specific group. However, some o ...

Retrieving the slug from the parameters in the API response using this.$route

I am currently using vue-router to navigate from an 'index' page displaying records for a particular resource. I have set up a router-link to direct you to a separate page for each individual record. Although the route is functioning correctly, I ...

How to visually represent options without labels using icons in Material UI Autocomplete

My options are structured as follows: const options = ['option1', 'option2']; I am looking to display the options with icons like this: https://i.stack.imgur.com/aubHS.png The current code for rendering options looks like this: ...

iOS now supports fully transparent tooltips and modals, offering a sleek and

I am currently working on developing tooltips and modals for an iOS app using react-native. The problem I am encountering is that the background of the tooltips and modals is not transparent, although it appears correctly on the Android version of the app. ...

I am unable to retrieve images using the querySelector method

Trying to target all images using JavaScript, here is the code: HTML : <div class="container"> <img src="Coca.jpg" class="imgg"> <img src="Water.jpg" class="imgg"> <img src="Tree.jpg" class="imgg"> <img src="Alien.jpg" class=" ...

Verify whether the chosen options from a dropdown list coincide with a predetermined value

I am working with a form that generates dropdown lists dynamically. <c:forEach var="companyList" items="${company.detailList}" varStatus="status"> <tr> <td>c:out value="${companyList.employeeName}" /></td> <td> <select ...

How can Swiper efficiently display the next set of x slides?

After exploring SwiperJS at https://swiperjs.com/, I've been unable to locate an option that allows the slide to return immediately to the right once it goes out of view on the left. The current behavior poses a problem where there is no next slide o ...