In Vue.js development, props are designed for one-way data flow: data flows down from a parent component to a child component. This principle helps keep your application’s data predictable and traceable.
Attempting to directly change a prop’s value within the child component is considered an anti-pattern and will trigger a warning in development mode (“Attempting to mutate prop ‘propName’. Props are immutable!”).
Why Direct Prop Mutation is Discouraged
Directly changing a prop in a child is bad because it:
- Confuses Data Origin: Hard to know where changes come from.
- Causes Side Effects: Can unexpectedly affect other parts of your app.
- Triggers Vue Warnings: Vue helps you avoid this anti-pattern.
Correct Ways to “Update” a Prop from a Child Component
To “update” a prop from a child, the child needs to tell its parent. The parent then updates its own data, which correctly flows down as a new prop. This is the “prop down, event up” pattern.
1. Prop Down, Event Up with v-model (Recommended)
This is the standard and most robust way to handle prop updates. The child component emits an event, passing along the new value. The parent component listens for this event and updates its own state, which then correctly flows down to the child as a prop.
<!– ParentComponent.vue –><template>
<ChildComponent v-model:title=”title” />
</template>
<script setup>
import { ref } from ‘vue’
import ChildComponent from ‘./ChildComponent.vue’
const title = ref(‘Initial Title’)
</script>
<!– ChildComponent.vue –>
<script setup>
import { defineProps, defineEmits } from ‘vue’
const props = defineProps([‘title’])
const emit = defineEmits([‘update:title’])
function updateTitle() {
emit(‘update:title’, ‘Updated Title’)
}
</script>
<template>
<h1>{{ props.title }}</h1>
<button @click=”updateTitle”>Update Title</button>
</template>
2. Using a Local Data Copy (for Initial Values)
If the prop is just for an initial value, and the child manages its own state independently afterward.
<template> <div>
<p>Local Count: {{ currentCount }}</p>
<button @click=”currentCount++”>Increment</button>
</div>
</template>
<script setup>
import { defineProps, ref } from ‘vue’;
const props = defineProps({
initialCount: { type: Number, default: 0 }
});
// Create local state, initialized from the prop
const currentCount = ref(props.initialCount);
// Note: If ‘initialCount’ changes in the parent, ‘currentCount’ won’t auto-update.
// Add ‘watch’ if you need it to react to parent prop changes later.
</script>
Also Read: Vue Component Libraries to Build Stunning Frontends
3. Using a computed Property (for Derived Values)
If you need a value that’s a calculation or transformation of props. computed properties automatically update when their source props change.
<template> <div>
<p>Total Cost: ${{ totalCost }}</p>
</div>
</template>
<script setup>
import { defineProps, computed } from ‘vue’;
const props = defineProps({
price: Number,
quantity: Number
});
// `totalCost` updates automatically if `price` or `quantity` props change.
const totalCost = computed(() => props.price * props.quantity);
</script>
Also Read: Vue 3 Composition API data() Function
Key Takeaway
Never directly change a prop in a Vue 3 component. Always use the “prop down, event up” pattern (especially with v-model for forms) to let the parent handle updates. For internal state or derived values, use ref (with optional watch) or computed properties. This maintains clear and predictable data flow.