Fix: Attempting to Mutate Prop in Vue.js

Error message:
Attempting to mutate prop "{key}". Props are readonly.
Props & Properties 2025-01-25

What Causes This Warning?

This warning occurs when you try to directly modify a prop value inside a child component. In Vue.js, props follow a one-way data flow - data flows down from parent to child, and props are readonly in the child component.

The Problem

<script setup>
const props = defineProps(['count'])

// ❌ Directly mutating prop
function increment() {
  props.count++ // Warning: Attempting to mutate prop "count"
}
</script>

The Fix

Option 1: Emit an Event to Parent

<!-- Child Component -->
<script setup>
const props = defineProps(['count'])
const emit = defineEmits(['update:count'])

// ✅ Emit event to parent
function increment() {
  emit('update:count', props.count + 1)
}
</script>

<!-- Parent Component -->
<ChildComponent :count="count" @update:count="count = $event" />

Option 2: Use v-model (Vue 3.4+)

<!-- Child Component -->
<script setup>
const count = defineModel('count')

// ✅ Modify the model directly
function increment() {
  count.value++
}
</script>

<!-- Parent Component -->
<ChildComponent v-model:count="count" />

Option 3: Use Local State

<script setup>
const props = defineProps(['initialCount'])

// ✅ Create local copy
const localCount = ref(props.initialCount)

function increment() {
  localCount.value++
}
</script>

Option 4: Use Computed with Setter

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

// ✅ Computed property with setter
const value = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
</script>

Common Scenarios

Form Input Binding

<!-- ❌ Wrong -->
<script setup>
const props = defineProps(['username'])
</script>
<template>
  <input v-model="username" /> <!-- Mutates prop! -->
</template>

<!-- ✅ Correct -->
<script setup>
const props = defineProps(['username'])
const emit = defineEmits(['update:username'])

const localUsername = computed({
  get: () => props.username,
  set: (val) => emit('update:username', val)
})
</script>
<template>
  <input v-model="localUsername" />
</template>

Array/Object Props

<!-- ❌ Wrong - mutating object prop -->
<script setup>
const props = defineProps(['user'])

function updateName(name) {
  props.user.name = name // Mutates prop object!
}
</script>

<!-- ✅ Correct - emit full new object -->
<script setup>
const props = defineProps(['user'])
const emit = defineEmits(['update:user'])

function updateName(name) {
  emit('update:user', { ...props.user, name })
}
</script>

Why Props Are Readonly

  1. Predictable data flow: Makes debugging easier
  2. Single source of truth: Parent owns the data
  3. Prevents side effects: Child can’t unexpectedly change parent state
  4. Better component design: Forces explicit communication

Quick Checklist

  • Never directly assign to props.x
  • Use emit() to notify parent of changes
  • Consider defineModel() for two-way binding
  • Use local ref() if you need a mutable copy
  • Remember object/array props can still be mutated (avoid this!)