React

watch and computed in Vue 3: when to use which

computed produces a value with no side effects, watch runs code when a source changes. The choice depends on the output: a piece of data or an effect.

Key points

  • If the function returns a value it is a computed; if it runs an effect it is a watch.
  • computed caches the result and recalculates only when a dependency changes.
  • watch declares its sources, receives old and new value, and accepts a cleanup to abort requests.

Use computed when you need a value derived from other state, and watch when you need to run an effect: a request, a log entry, a save. If the function returns nothing, it is not a computed.

Computed: a derived value


import { computed, ref } from "vue";

const query = ref("");
const rows = ref([]);

const visible = computed(() =>
  rows.value.filter((row) =>
    row.name.toLowerCase().includes(query.value.toLowerCase()),
  ),
);

The filter runs once per change of query or rows, not on every render. computed caches the result until a dependency changes, which is the practical difference from a method called in the template.

Watch: an effect with a source

import { ref, watch } from "vue";

const id = ref("1");
const user = ref(null);

watch(
  id,
  async (value, _old, onCleanup) => {
    const controller = new AbortController();
    onCleanup(() => controller.abort());
    const res = await fetch("/api/users/" + value, {
      signal: controller.signal,
    });
    user.value = await res.json();
  },
  { immediate: true },
);

Here the result is not a value to display but a request to fire. immediate runs at once, and onCleanup aborts the previous call when id changes or the component unmounts.

The choice in three lines

CaseWinnerWhy
Cart totalcomputedA derived value, no effect
Autosave of the draftwatchWrites outside state, so it needs an effect
Page title from the user namewatchIt touches document.title, not a value
Filtering a listcomputedCached and readable in the template

Common mistakes

  • Assigning inside a computed: the compiler warns and the cache becomes unpredictable.
  • Using watchEffect for a derived value: it collects dependencies implicitly and recalculates unchecked.
  • Watching a large object with deep: true: the traversal costs more than it gives.
  • Skipping the cleanup: two requests in flight write state in the wrong order.

The limitation to plan for

Neither function solves the first load: an async computed returns a Promise, not the data. For a value that arrives over the network you need a ref plus a watch with cleanup, as in the example above.

Where to go next

In the catalogue: shadcn-vue, PrimeVue and Vuetify; filtered tables are in components. The guides: Composition API and Pinia. The comparisons: derived state in React and Vue vs React.

Frequently asked questions

Can I use computed inside watch?

Yes, and it often helps: watching a computed reacts to the derived value instead of every field that builds it.

watch or watchEffect?

watch declares its sources and receives old and new value; watchEffect collects dependencies by itself and suits short effects.

Can computed be async?

No. An async function inside computed returns a Promise and the value is never awaited, so an async datum needs watch plus a ref.

Why does my watch never run?

The source is not reactive. A getter over a plain variable notifies nothing, so pass a ref or a function that reads one.

Should I use watch with deep?

Only for objects you change in depth. On large structures the traversal costs more than it gives, and watching one field is cheaper.