Files
agent-skills/skills/vue-best-practices/reference/attrs-not-reactive.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

163 lines
4.2 KiB
Markdown

---
title: useAttrs() Object Is Not Reactive
impact: MEDIUM
impactDescription: Watching attrs directly does not trigger - use onUpdated() or convert to props
type: gotcha
tags: [vue3, attrs, reactivity, composition-api]
---
# useAttrs() Object Is Not Reactive
**Impact: MEDIUM** - The object returned by `useAttrs()` is NOT reactive. While it always reflects the latest fallthrough attributes, you cannot use `watch()` or `watchEffect()` to observe its changes. Watchers on attrs properties will NOT trigger when attributes change.
## Task Checklist
- [ ] Never use `watch()` to observe attrs changes - it won't trigger
- [ ] Use `onUpdated()` lifecycle hook for side effects based on attrs
- [ ] Convert frequently-accessed attrs to props if you need reactivity
- [ ] Remember attrs ARE always current in templates and event handlers
**Incorrect:**
```vue
<script setup>
import { watch, useAttrs } from 'vue'
const attrs = useAttrs()
// WRONG: This watcher will NEVER trigger when attrs change!
watch(
() => attrs.someAttr,
(newValue) => {
console.log('Attribute changed:', newValue)
// This callback never runs on attr changes
}
)
// WRONG: watchEffect also doesn't track attrs
watchEffect(() => {
console.log(attrs.class) // Only runs on mount, NOT when class changes
})
</script>
```
**Correct:**
```vue
<script setup>
import { onUpdated, useAttrs } from 'vue'
const attrs = useAttrs()
// CORRECT: Use onUpdated to access latest attrs
onUpdated(() => {
console.log('Current attrs:', attrs)
// Perform side effects with latest attrs here
})
</script>
```
```vue
<script setup>
import { watch } from 'vue'
// CORRECT: If you need reactivity, declare it as a prop
const props = defineProps({
someAttr: String
})
// Now you can watch it
watch(
() => props.someAttr,
(newValue) => {
console.log('Attribute changed:', newValue)
}
)
</script>
```
## Why Attrs Are Not Reactive
Vue's official documentation states:
> "Note that although the attrs object here always reflects the latest fallthrough attributes, it isn't reactive (for performance reasons). You cannot use watchers to observe its changes."
This is a deliberate design decision for performance - making attrs reactive would add overhead to every component that uses fallthrough attributes.
## When Attrs DO Reflect Current Values
Despite not being reactive, attrs always have current values in these contexts:
```vue
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
// In event handlers - attrs are current when handler executes
function handleClick() {
console.log(attrs.class) // Current value at click time
}
</script>
<template>
<!-- In templates - always current -->
<div :class="attrs.class">
{{ attrs.title }}
</div>
<!-- Using v-bind to spread all attrs -->
<input v-bind="attrs" />
</template>
```
## Computed Properties with Attrs
Computed properties that reference attrs will update when the component re-renders:
```vue
<script setup>
import { computed, useAttrs } from 'vue'
const attrs = useAttrs()
// This DOES update - because computed re-evaluates on render
const hasCustomClass = computed(() => {
return !!attrs.class
})
</script>
<template>
<div v-bind="attrs">
<span v-if="hasCustomClass">Has custom styling</span>
</div>
</template>
```
Note: The computed updates because the component re-renders when props/attrs change, not because attrs is reactive.
## Alternative: Use getCurrentInstance() (Advanced)
For advanced use cases, you can access attrs through the component instance:
```vue
<script setup>
import { watch, getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
// This may work in some cases, but is NOT officially supported
// getCurrentInstance() is an internal API and may change
watch(
() => instance?.attrs.someAttr,
(newValue) => {
console.log('Attribute changed:', newValue)
}
)
</script>
```
> **Warning:** `getCurrentInstance()` is an internal API. Prefer `onUpdated()` or converting to props.
## Reference
- [Fallthrough Attributes - Accessing in JavaScript](https://vuejs.org/guide/components/attrs.html#accessing-fallthrough-attributes-in-javascript)
- [Vue 3 Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html)