Fix: setupContext.attrs Is Readonly in Vue.js

Error message:
setupContext.attrs is readonly.
Composition API 2025-01-25

What Causes This Warning?

This warning occurs when you try to modify the attrs object from the setup context. The attrs object is reactive and readonly - it reflects attributes passed from the parent but cannot be directly modified.

The Problem

export default {
  setup(props, { attrs }) {
    // ❌ Trying to modify attrs
    attrs.class = 'new-class'
    delete attrs.id
  }
}

The Fix

Copy Attrs When Needed

export default {
  setup(props, { attrs }) {
    // ✅ Create a copy if you need to modify
    const modifiedAttrs = { ...attrs }
    modifiedAttrs.class = 'modified-class'

    return () => h('div', modifiedAttrs, 'Content')
  }
}

Use Computed for Derived Attrs

<script setup>
import { computed, useAttrs } from 'vue'

const attrs = useAttrs()

// ✅ Computed property for modified attrs
const enhancedAttrs = computed(() => ({
  ...attrs,
  class: `enhanced ${attrs.class || ''}`
}))
</script>

Common Scenarios

Adding Default Classes

<script setup>
import { computed, useAttrs } from 'vue'

const attrs = useAttrs()

// ✅ Merge with default classes
const buttonAttrs = computed(() => ({
  ...attrs,
  class: ['btn', 'btn-primary', attrs.class].filter(Boolean).join(' ')
}))
</script>

<template>
  <button v-bind="buttonAttrs">
    <slot />
  </button>
</template>

Filtering Attrs

<script setup>
import { computed, useAttrs } from 'vue'

const attrs = useAttrs()

// ✅ Filter out specific attributes
const filteredAttrs = computed(() => {
  const { class: _, style: __, ...rest } = attrs
  return rest
})

const styleAttrs = computed(() => ({
  class: attrs.class,
  style: attrs.style
}))
</script>

<template>
  <div v-bind="styleAttrs">
    <input v-bind="filteredAttrs" />
  </div>
</template>

Options API

export default {
  inheritAttrs: false,
  setup(props, { attrs }) {
    // ✅ Return function that uses attrs reactively
    return () => {
      const mergedAttrs = {
        ...attrs,
        'data-custom': 'value'
      }

      return h('div', mergedAttrs, 'Content')
    }
  }
}

Watching Attrs

<script setup>
import { useAttrs, watch } from 'vue'

const attrs = useAttrs()

// ✅ You can watch attrs (they're reactive)
watch(() => attrs.class, (newClass) => {
  console.log('Class changed:', newClass)
})
</script>

Quick Checklist

  • Never directly modify attrs
  • Use spread operator to create copies
  • Use computed for reactive derived attrs
  • attrs is reactive, so it updates automatically
  • Use useAttrs() in script setup