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>
148 lines
4.3 KiB
Markdown
148 lines
4.3 KiB
Markdown
---
|
|
title: Ensure All Dependencies Are Accessed in Computed Properties
|
|
impact: HIGH
|
|
impactDescription: Conditional logic can prevent dependency tracking causing stale computed values
|
|
type: capability
|
|
tags: [vue3, computed, reactivity, dependency-tracking, gotcha]
|
|
---
|
|
|
|
# Ensure All Dependencies Are Accessed in Computed Properties
|
|
|
|
**Impact: HIGH** - Vue tracks computed property dependencies by monitoring which reactive properties are accessed during execution. If conditional logic prevents a property from being accessed on the first run, Vue won't track it as a dependency, causing the computed property to not update when that property changes.
|
|
|
|
This is a subtle but common source of bugs, especially with short-circuit evaluation (`&&`, `||`) and early returns.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Access all reactive dependencies before any conditional logic
|
|
- [ ] Be cautious with short-circuit operators (`&&`, `||`) that may skip property access
|
|
- [ ] Store all dependencies in variables at the start of the computed getter
|
|
- [ ] Test computed properties with different initial states
|
|
|
|
**Incorrect:**
|
|
```vue
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
|
|
const isEnabled = ref(false)
|
|
const data = ref('important data')
|
|
|
|
// BAD: If isEnabled is false initially, data.value is never accessed
|
|
// Vue won't track 'data' as a dependency!
|
|
const result = computed(() => {
|
|
if (!isEnabled.value) {
|
|
return 'disabled'
|
|
}
|
|
return data.value // This dependency may not be tracked
|
|
})
|
|
|
|
// BAD: Short-circuit prevents second access
|
|
const password = ref('')
|
|
const confirmPassword = ref('')
|
|
|
|
const isValid = computed(() => {
|
|
// If password is empty, confirmPassword is never accessed
|
|
return password.value && password.value === confirmPassword.value
|
|
})
|
|
|
|
// BAD: Early return prevents dependency access
|
|
const user = ref(null)
|
|
const permissions = ref(['read', 'write'])
|
|
|
|
const canEdit = computed(() => {
|
|
if (!user.value) {
|
|
return false // permissions.value never accessed when user is null
|
|
}
|
|
return permissions.value.includes('write')
|
|
})
|
|
</script>
|
|
```
|
|
|
|
**Correct:**
|
|
```vue
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
|
|
const isEnabled = ref(false)
|
|
const data = ref('important data')
|
|
|
|
// GOOD: Access all dependencies first
|
|
const result = computed(() => {
|
|
const enabled = isEnabled.value
|
|
const currentData = data.value // Always accessed
|
|
|
|
if (!enabled) {
|
|
return 'disabled'
|
|
}
|
|
return currentData
|
|
})
|
|
|
|
// GOOD: Access both values before comparison
|
|
const password = ref('')
|
|
const confirmPassword = ref('')
|
|
|
|
const isValid = computed(() => {
|
|
const pwd = password.value
|
|
const confirm = confirmPassword.value // Always accessed
|
|
|
|
return pwd && pwd === confirm
|
|
})
|
|
|
|
// GOOD: Access all reactive sources upfront
|
|
const user = ref(null)
|
|
const permissions = ref(['read', 'write'])
|
|
|
|
const canEdit = computed(() => {
|
|
const currentUser = user.value
|
|
const currentPermissions = permissions.value // Always accessed
|
|
|
|
if (!currentUser) {
|
|
return false
|
|
}
|
|
return currentPermissions.includes('write')
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## The Dependency Tracking Mechanism
|
|
|
|
Vue's reactivity system works by tracking which reactive properties are accessed when a computed property runs:
|
|
|
|
```javascript
|
|
// How Vue tracks dependencies (simplified):
|
|
// 1. Start tracking
|
|
// 2. Run the getter function
|
|
// 3. Record every .value or reactive property access
|
|
// 4. Stop tracking
|
|
|
|
const computed = computed(() => {
|
|
// Vue starts tracking here
|
|
if (conditionA.value) { // conditionA is tracked
|
|
return valueB.value // valueB is ONLY tracked if conditionA is true
|
|
}
|
|
return 'default' // If conditionA is false, valueB is NOT tracked!
|
|
})
|
|
```
|
|
|
|
## Pattern: Destructure All Dependencies First
|
|
|
|
```javascript
|
|
// GOOD PATTERN: Destructure/access everything at the top
|
|
const result = computed(() => {
|
|
// Access all potential dependencies
|
|
const { user, settings, items } = toRefs(store)
|
|
const userVal = user.value
|
|
const settingsVal = settings.value
|
|
const itemsVal = items.value
|
|
|
|
// Now use conditional logic safely
|
|
if (!userVal) return []
|
|
if (!settingsVal.enabled) return []
|
|
return itemsVal.filter(i => i.active)
|
|
})
|
|
```
|
|
|
|
## Reference
|
|
- [Vue.js Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)
|
|
- [GitHub Discussion: Dependency collection gotcha with conditionals](https://github.com/vuejs/Discussion/issues/15)
|