Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/reactivity-computed-over-watcheffect-mutations.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

3.8 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Use computed() Instead of watchEffect() for Derived State MEDIUM Using watchEffect to mutate refs creates unnecessary indirection - computed() is declarative and cached efficiency
vue3
reactivity
computed
watchEffect
best-practice
performance

Use computed() Instead of watchEffect() for Derived State

Impact: MEDIUM - When you need state that derives from other reactive state, always prefer computed() over using watchEffect() to mutate a ref. Computed properties are declarative, automatically cached, and clearly express the dependency relationship.

Using watchEffect() to mutate a ref works but creates unnecessary indirection: you're imperatively updating state based on dependencies rather than declaring the relationship. This makes the code harder to understand and prevents Vue from optimizing.

Task Checklist

  • Use computed() when the result is a pure transformation of reactive state
  • Use watchEffect() only for side effects (DOM manipulation, logging, API calls)
  • Never use watchEffect to mutate a ref just to derive a value
  • Remember computed values are cached and only re-compute when dependencies change

Incorrect:

import { ref, watchEffect } from 'vue';

const A0 = ref(1);
const A1 = ref(2);
const A2 = ref(); // Unnecessary ref

// WRONG: Using watchEffect to derive state
watchEffect(() => {
  A2.value = A0.value + A1.value;
});

// Problems:
// 1. A2 is writable when it shouldn't be
// 2. Imperative instead of declarative
// 3. No caching optimization
// 4. Harder to trace dependencies
// WRONG: Complex derived state with watchEffect
const items = ref([{ price: 10 }, { price: 20 }]);
const total = ref(0);

watchEffect(() => {
  total.value = items.value.reduce((sum, item) => sum + item.price, 0);
});

Correct:

import { ref, computed } from 'vue';

const A0 = ref(1);
const A1 = ref(2);

// CORRECT: Declarative derived state
const A2 = computed(() => A0.value + A1.value);

// Benefits:
// 1. A2 is read-only by default
// 2. Clearly declares the dependency relationship
// 3. Cached - only recalculates when A0 or A1 changes
// 4. Easy to understand data flow
// CORRECT: Complex derived state with computed
const items = ref([{ price: 10 }, { price: 20 }]);

const total = computed(() => {
  return items.value.reduce((sum, item) => sum + item.price, 0);
});

// Multiple derived values
const itemCount = computed(() => items.value.length);
const averagePrice = computed(() => (items.value.length ? total.value / itemCount.value : 0));

When watchEffect IS appropriate:

import { ref, watchEffect } from 'vue';

const searchQuery = ref('');

// CORRECT: watchEffect for side effects
watchEffect(() => {
  // Logging
  console.log(`Search query changed: ${searchQuery.value}`);

  // DOM manipulation
  document.title = `Search: ${searchQuery.value}`;
});

// CORRECT: watchEffect for async side effects
watchEffect(async () => {
  if (searchQuery.value) {
    // API call (side effect, not derived state)
    await api.logSearch(searchQuery.value);
  }
});

Summary of when to use each:

// Use computed() when:
// - You're deriving a value from reactive state
// - The result is pure (no side effects)
// - You want caching
const fullName = computed(() => `${firstName.value} ${lastName.value}`);

// Use watchEffect() when:
// - You need to perform side effects
// - You're interacting with external systems
// - You need to run async operations
watchEffect(() => {
  document.title = fullName.value; // Side effect
});

Reference