Lifecycle Hooks (Composition + Options Side by Side)
TL;DR
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.
Interview Q&A
Q: List the lifecycle hooks and what each is for.
A:
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 |
Q: Show both APIs for the common cases.
A:
<script setup>
import { onMounted, onBeforeUnmount } from "vue";
onMounted(() => {
console.log("mounted, DOM ready");
});
onBeforeUnmount(() => {
cleanup();
});
</script>
<script>
export default {
mounted() {
console.log("mounted, DOM ready");
},
beforeUnmount() {
this.cleanup();
},
};
</script>
Q: When does mounted fire relative to child components?
A: Child mounted fires before parent mounted. The tree mounts depth-first — leaves first, then their parents. Same for unmounted (reverse: children unmount first).
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.
Q: Difference between onUpdated and watch?
A: onUpdated fires on every re-render of the component, regardless of cause. watch fires when a specific reactive value changes.
// 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.
Q: Why does onMounted need to be called synchronously in setup?
A: 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.
// Wrong — conditional registration
if (showFeature) {
onMounted(() => { /* ... */ });
}
// Wrong — after await
const data = await fetch(...);
onMounted(() => { /* ... */ }); // setup already passed
// Right
onMounted(() => {
if (showFeature) { /* ... */ }
});
Q: onErrorCaptured — what’s the contract?
A: Catches errors thrown synchronously, in async handlers, and in lifecycle hooks of descendants. Returning false stops propagation.
<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.
Q: onRenderTracked / onRenderTriggered — when do you reach for these?
A: 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?”
onRenderTriggered((event) => {
console.log("re-render triggered by:", event.key, event.target);
});
Modern Vue DevTools surface this graphically; you rarely need these by hand.
Q: Vue 2 → Vue 3 lifecycle renames.
A:
| 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).
Q: SSR-specific: onServerPrefetch.
A: 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.
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 / edge cases
- Don’t put async code in
mountedand assume the DOM is stable — by the time the await resolves, the component may have re-rendered or unmounted. CheckisMounted/ abort signal. onMounteddoesn’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, useonServerPrefetch.onUpdatedandnextTick— 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 getonDeactivated/onActivatedinstead. Long-running effects need to pause/resume accordingly.- Composables registering hooks —
onMountedinside a composable is bound to the consuming component. The composable runs in the consumer’ssetup, registering the hook there. - Errors in
setupitself are NOT caught byonErrorCapturedof the same component — they propagate to the parent’sonErrorCaptured.
What a senior is expected to say
- “
onMountedfor DOM-ready work,onBeforeUnmountfor cleanup,onErrorCapturedfor error boundaries,watchfor specific value reactions,onUpdatedonly when you genuinely need every render.” - “Mount order is depth-first: children mount before parents. So a parent’s
onMountedsees fully-mounted children.” - “Composition hooks register during
setup, synchronously — same hook-rule constraint as React, same reason.” - “
onServerPrefetch(or top-level await in<script setup>) for SSR data; client-only effects belong inonMountedwhich doesn’t fire on the server.” - “
<KeepAlive>swapsunmountedfordeactivated— your effects need to pause/resume, not setup/teardown.”
Cross-references
- Composition vs Options style: 05_composition_vs_options.md
- Reactivity /
watch(vsonUpdated): 04_computed_watch_watcheffect.md <KeepAlive>and advanced features: 12_advanced_features.md
Further reading
- Vue docs — Lifecycle Hooks: https://vuejs.org/guide/essentials/lifecycle.html
- Vue API — Reactivity & Lifecycle: https://vuejs.org/api/composition-api-lifecycle.html