Frontend / Vue / 06_lifecycle_hooks.md

Lifecycle Hooks (Composition + Options Side by Side)

Updated 5 interview angles 6 min read source
On this page13
  1. List the lifecycle hooks and what each is for.
  2. Show both APIs for the common cases.
  3. When does mounted fire relative to child components?
  4. Difference between onUpdated and watch?
  5. Why does onMounted need to be called synchronously in setup?
  6. onErrorCaptured — what’s the contract?
  7. onRenderTracked / onRenderTriggered — when do you reach for these?
  8. Vue 2 → Vue 3 lifecycle renames.
  9. SSR-specific: onServerPrefetch.
  10. Gotchas
  11. Related
  12. Further reading
  13. Interview angle

Lifecycle Hooks (Composition + Options Side by Side)

Same lifecycle, two APIs. Composition API uses imported functions inside setup (onMounted(() => ...)); Options API uses named options (mounted() { ... }). Vue 3 renamed beforeDestroy/destroyed → beforeUnmount/unmounted and added concurrent-aware hooks (onRenderTracked/onRenderTriggered) for debugging reactivity. The Composition hooks must be registered synchronously during setup — same constraint as React hooks.

List the lifecycle hooks and what each is for.

Composition (in setup) Options API Fires
(setup body itself) beforeCreate, created before the component instance is created (rarely needed in Composition API; the setup body itself runs at this stage)
onBeforeMount beforeMount before first render → DOM
onMounted mounted after first render; DOM available
onBeforeUpdate beforeUpdate after state changed, before DOM update
onUpdated updated after DOM update from reactive change
onBeforeUnmount beforeUnmount before component is removed
onUnmounted unmounted after component is removed (cleanup)
onErrorCaptured errorCaptured catches errors from descendants
onActivated activated for <KeepAlive> — when re-entered
onDeactivated deactivated for <KeepAlive> — when cached & hidden
onRenderTracked (dev) n/a a reactive dep was tracked during render
onRenderTriggered (dev) n/a a reactive dep triggered a re-render
onServerPrefetch serverPrefetch SSR-only — async data fetch before render

Show both APIs for the common cases.

vue
<script setup>
import { onMounted, onBeforeUnmount } from "vue";

onMounted(() => {
  console.log("mounted, DOM ready");
});

onBeforeUnmount(() => {
  cleanup();
});
</script>
vue
<script>
export default {
  mounted() {
    console.log("mounted, DOM ready");
  },
  beforeUnmount() {
    this.cleanup();
  },
};
</script>

When does mounted fire relative to child components?

Child mounted fires before parent mounted. The tree mounts depth-first — leaves first, then their parents. Same for unmounted (reverse: children unmount first).

text
Parent.beforeMount
  Child.beforeMount
  Child.mounted        ← child first
Parent.mounted          ← then parent

This matters when a parent needs to interact with a mounted child (e.g., measure dimensions) — by the time Parent.mounted runs, children are already mounted.

Difference between onUpdated and watch?

onUpdated fires on every re-render of the component, regardless of cause. watch fires when a specific reactive value changes.

ts
// Fires on every render — usually too coarse
onUpdated(() => console.log("re-rendered"));

// Fires only when count changes
watch(count, () => console.log("count changed"));

Use onUpdated for things genuinely needing to run on every render (animation tied to layout, after-render DOM measurement); use watch for everything else.

Why does onMounted need to be called synchronously in setup?

Same reason as React hooks: the registration order ties to the lifecycle binding. Calling it inside an if or after an await means it may or may not register on a given setup run, breaking the contract.

ts
// Wrong — conditional registration
if (showFeature) {
  onMounted(() => { /* ... */ });
}

// Wrong — after await
const data = await fetch(...);
// setup already passed
onMounted(() => { /* ... */ });

// Right
onMounted(() => {
  if (showFeature) { /* ... */ }
});

onErrorCaptured — what’s the contract?

Catches errors thrown synchronously, in async handlers, and in lifecycle hooks of descendants. Returning false stops propagation.

vue
<script setup>
import { onErrorCaptured } from "vue";

onErrorCaptured((err, instance, info) => {
  reportError(err, info);
  return false;     // stop propagation
});
</script>

Acts like React’s error boundary. Place at app root + key boundaries. Errors in the current component aren’t caught by its own handler; they’re caught by an ancestor.

onRenderTracked / onRenderTriggered — when do you reach for these?

Dev-only debugging hooks. onRenderTracked fires when a reactive dep is tracked during render; onRenderTriggered fires when a tracked dep is the cause of a re-render. Useful for “why did this component re-render?”

ts
onRenderTriggered((event) => {
  console.log("re-render triggered by:", event.key, event.target);
});

Modern Vue DevTools surface this graphically; you rarely need these by hand.

Vue 2 → Vue 3 lifecycle renames.

Vue 2 Vue 3
beforeCreate / created replaced by setup() body (Composition)
beforeDestroy beforeUnmount
destroyed unmounted
n/a onErrorCaptured (existed in Vue 2, now in Composition too)
n/a onRenderTracked / onRenderTriggered (Vue 3 new)

Old names still work in Vue 2; Vue 3 supports both old and new (with beforeDestroy warning as deprecated in some setups).

SSR-specific: onServerPrefetch.

Async data fetch that runs during server rendering. Component waits for the returned promise before rendering. Used in plain Vue SSR; Nuxt has its own useAsyncData/useFetch higher-level APIs.

ts
const post = ref(null);
onServerPrefetch(async () => {
  post.value = await fetchPost();
});

In Composition API + <script setup>, top-level await also serves this purpose (turning the component into an async component, requires <Suspense> upstream).

Gotchas

  • Don’t put async code in mounted and assume the DOM is stable — by the time the await resolves, the component may have re-rendered or unmounted. Check isMounted / abort signal.
  • onMounted doesn’t fire during SSR. Components mount on the server but lifecycle hooks tied to the browser don’t run (or run on hydration on the client). For SSR data, use onServerPrefetch.
  • onUpdated and nextTick — DOM updates are batched and async. To read post-update DOM from an event handler, await nextTick().
  • <KeepAlive> changes the lifecycle — components inside aren’t unmounted on hide; they get onDeactivated/onActivated instead. Long-running effects need to pause/resume accordingly.
  • Composables registering hooks — onMounted inside a composable is bound to the consuming component. The composable runs in the consumer’s setup, registering the hook there.
  • Errors in setup itself are NOT caught by onErrorCaptured of the same component — they propagate to the parent’s onErrorCaptured.

Further reading

Interview angle 5

  • “Which lifecycle hook for what?” - onMounted for anything needing the DOM, onBeforeUnmount for teardown, onErrorCaptured for an error boundary, and watch when you are actually reacting to one value rather than to rendering. onUpdated only when you genuinely need every render, which is rare and usually a smell.
  • “In what order do parent and child mount?” - depth-first: children mount before parents. So a parent’s onMounted can safely assume its children are mounted, and a child’s cannot assume anything about the parent.
  • “Why must Composition hooks be registered synchronously in setup?” - because Vue binds them to the current component instance, and that binding only exists during synchronous setup. Registering one after an await attaches it to nothing - the same constraint as React’s hook rules, for a similar reason.
  • “How do you fetch data for SSR?” - onServerPrefetch, or top-level await in <script setup>. onMounted never runs on the server, so anything in it is client-only by definition - which is the right place for browser APIs and the wrong place for data the HTML should contain.
  • “What changes under <KeepAlive>?” - the component is cached rather than destroyed, so you get deactivated and activated instead of unmounted and mounted. Effects need to pause and resume rather than tear down and rebuild, and code that only handles unmount leaks timers and subscriptions.