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>
187 lines
4.7 KiB
Markdown
187 lines
4.7 KiB
Markdown
---
|
|
title: Choose watch vs watchEffect Based on Dependency Control Needs
|
|
impact: MEDIUM
|
|
impactDescription: Wrong choice leads to unnecessary re-runs or missed dependency tracking
|
|
type: efficiency
|
|
tags: [vue3, watch, watchEffect, watchers, reactivity, best-practices]
|
|
---
|
|
|
|
# Choose watch vs watchEffect Based on Dependency Control Needs
|
|
|
|
**Impact: MEDIUM** - Using `watch` when `watchEffect` would be cleaner leads to repetitive code. Using `watchEffect` when `watch` is needed can cause unexpected re-runs or missed dependencies (especially with async).
|
|
|
|
Use `watchEffect` for simple cases where the callback uses the same state as what should trigger it. Use `watch` when you need precise control over what triggers the callback, access to old values, or lazy execution.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Use `watchEffect` when callback logic uses the same state it should react to
|
|
- [ ] Use `watch` when you need old value comparison
|
|
- [ ] Use `watch` when you need lazy execution (not immediate)
|
|
- [ ] Use `watch` for async callbacks with dependencies after await
|
|
- [ ] Use `watch` when callback should not run on initial mount
|
|
|
|
## Comparison Table
|
|
|
|
| Feature | `watch` | `watchEffect` |
|
|
|---------|---------|---------------|
|
|
| Dependency tracking | Explicit (you specify) | Automatic (uses accessed properties) |
|
|
| Lazy by default | Yes (runs only on change) | No (runs immediately) |
|
|
| Access old value | Yes | No |
|
|
| Async dependency tracking | Full control | Only before first await |
|
|
| Multiple sources | Array syntax | Automatic |
|
|
|
|
**When to prefer `watchEffect`:**
|
|
```vue
|
|
<script setup>
|
|
import { ref, watchEffect } from 'vue'
|
|
|
|
const todoId = ref(1)
|
|
const data = ref(null)
|
|
|
|
// GOOD: watchEffect is cleaner when callback uses same state
|
|
watchEffect(async () => {
|
|
const response = await fetch(
|
|
`https://api.example.com/todos/${todoId.value}`
|
|
)
|
|
data.value = await response.json()
|
|
})
|
|
</script>
|
|
```
|
|
|
|
**When to prefer `watch`:**
|
|
```vue
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
|
|
const todoId = ref(1)
|
|
const data = ref(null)
|
|
|
|
// BETTER with watch when:
|
|
|
|
// 1. You need old value
|
|
watch(todoId, (newId, oldId) => {
|
|
console.log(`Changed from ${oldId} to ${newId}`)
|
|
})
|
|
|
|
// 2. You don't want immediate execution
|
|
watch(todoId, () => {
|
|
// Only runs when todoId changes, not on mount
|
|
fetchData()
|
|
})
|
|
|
|
// 3. You have dependencies after await
|
|
watch(todoId, async (id) => {
|
|
const response = await fetch(`/api/todos/${id}`)
|
|
// More reactive access here still triggers correctly
|
|
// because we explicitly specified todoId as the source
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Avoid Redundant Code with watchEffect
|
|
|
|
```vue
|
|
<script setup>
|
|
import { ref, watch, watchEffect } from 'vue'
|
|
|
|
const searchQuery = ref('')
|
|
const category = ref('all')
|
|
const results = ref([])
|
|
|
|
// BAD: Repetitive - listing same deps in source and using in callback
|
|
watch(
|
|
[searchQuery, category],
|
|
([query, cat]) => {
|
|
fetchResults(query, cat) // Same variables repeated
|
|
}
|
|
)
|
|
|
|
// GOOD: watchEffect removes repetition
|
|
watchEffect(() => {
|
|
fetchResults(searchQuery.value, category.value)
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Use watch for Lazy Behavior
|
|
|
|
```vue
|
|
<script setup>
|
|
import { ref, watch, watchEffect } from 'vue'
|
|
|
|
const userId = ref(null)
|
|
|
|
// BAD: Runs immediately even when userId is null
|
|
watchEffect(() => {
|
|
if (userId.value) {
|
|
loadUserProfile(userId.value)
|
|
}
|
|
})
|
|
|
|
// GOOD: Only runs when userId actually changes
|
|
watch(userId, (id) => {
|
|
if (id) {
|
|
loadUserProfile(id)
|
|
}
|
|
})
|
|
|
|
// ALSO GOOD: watch with immediate when you need both behaviors
|
|
watch(
|
|
userId,
|
|
(id) => {
|
|
if (id) loadUserProfile(id)
|
|
},
|
|
{ immediate: true } // Explicit about running immediately
|
|
)
|
|
</script>
|
|
```
|
|
|
|
## Use watch for Old Value Comparison
|
|
|
|
```vue
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
|
|
const status = ref('pending')
|
|
|
|
// Only watch() provides old value
|
|
watch(status, (newStatus, oldStatus) => {
|
|
if (oldStatus === 'pending' && newStatus === 'approved') {
|
|
showApprovalNotification()
|
|
}
|
|
|
|
if (oldStatus === 'approved' && newStatus === 'rejected') {
|
|
showRejectionWarning()
|
|
}
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Use watch for Complex Async Dependencies
|
|
|
|
```vue
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
|
|
const filters = ref({ status: 'active', sort: 'date' })
|
|
const page = ref(1)
|
|
const results = ref([])
|
|
|
|
// BETTER: watch with explicit sources for async
|
|
// All dependencies tracked regardless of await placement
|
|
watch(
|
|
[filters, page],
|
|
async ([currentFilters, currentPage]) => {
|
|
const data = await fetchWithFilters(currentFilters)
|
|
|
|
// These are still correctly tracked:
|
|
results.value = paginateResults(data, currentPage)
|
|
},
|
|
{ deep: true }
|
|
)
|
|
</script>
|
|
```
|
|
|
|
## Reference
|
|
- [Vue.js Watchers - watch vs. watchEffect](https://vuejs.org/guide/essentials/watchers.html#watch-vs-watcheffect)
|