Because Vue enforces one-way data flow: props go down (parent to child), events go up (child to parent). If a child modifies a prop directly, the parent doesn't know about it, and the next time the parent re-renders, it will overwrite the child's change.
vue
<script setup lang="ts">
const props = defineProps<{ count: number }>()
function increment() {
props.count++ // ⚠️ Warning: Attempting to mutate prop "count"
}
</script>Vue shows this warning because it's almost always a bug. The data owner (parent) and the data mutator (child) are out of sync.
How to fix it
Option 1: Emit an event and let the parent handle the change.
vue
<!-- Child -->
<script setup lang="ts">
const props = defineProps<{ count: number }>()
const emit = defineEmits<{ update: [value: number] }>()
function increment() {
emit('update', props.count + 1)
}
</script>
<!-- Parent -->
<Counter :count="count" @update="count = $event" />Option 2: Use v-model (shortcut for the pattern above).
vue
<!-- Child -->
<script setup lang="ts">
const count = defineModel<number>()
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
<!-- Parent -->
<Counter v-model="count" />Option 3: Use a local copy if the prop is just an initial value.
ts
const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount)Name the prop initialX to signal that it's only used once and won't stay in sync with the parent.