iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Lifecycle Hooks

A Vue component goes through lifecycle stages — setup, mount, update, unmount. Composition API exposes them as onMounted, onUpdated, onBeforeUnmount. Use for non-reactive side effects.

Hooks + suspense + keep-alive

EXAMPLE
<script setup>
import {
    onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
    onBeforeUnmount, onUnmounted,
    onErrorCaptured, onRenderTracked, onRenderTriggered,
    onActivated, onDeactivated,
    ref, watchEffect,
} from 'vue';

const el = ref(null);
const online = ref(navigator.onLine);

// 1) Mount — DOM is ready
onMounted(() => {
    el.value.focus();
    const onChange = () => online.value = navigator.onLine;
    window.addEventListener('online',  onChange);
    window.addEventListener('offline', onChange);
});

// 2) Cleanup
onBeforeUnmount(() => {
    // remove the listeners
});

// Or use watchEffect with onCleanup for self-contained subscriptions
import { watchEffect } from 'vue';
watchEffect((onCleanup) => {
    const t = setInterval(() => poll(), 5000);
    onCleanup(() => clearInterval(t));
});

// 3) Update hooks — runs after every reactive update
onUpdated(() => {
    // DOM has just been rerendered. Avoid setting state here (loops!)
});

// 4) Catch errors from descendant components
onErrorCaptured((err, instance, info) => {
    console.error('caught from child:', err, info);
    return false;     // prevent propagation
});

// 5) Lifecycle hooks in plain JS modules (composables)
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useMouse() {
    const x = ref(0), y = ref(0);
    const update = (e) => { x.value = e.clientX; y.value = e.clientY; };
    onMounted(()    => window.addEventListener('mousemove', update));
    onUnmounted(() => window.removeEventListener('mousemove', update));
    return { x, y };
}

// 6) onActivated / onDeactivated — only inside <keep-alive>
onActivated(()   => console.log('cached component shown'));
onDeactivated(() => console.log('cached component hidden'));
</script>

<template>
    <input ref="el" :placeholder="online ? 'online' : 'offline'" />
</template>

<!-- 7) Full lifecycle order (Composition API) -->
<!--
  setup()                                    — script setup runs first
  onBeforeMount()                            — about to mount
  onMounted()                                — DOM mounted
  onBeforeUpdate() / onUpdated()             — reactive update
  onBeforeUnmount() / onUnmounted()          — teardown
  onErrorCaptured()                          — child error
  onActivated() / onDeactivated()            — keep-alive only
-->

<!-- 8) Options API equivalents (older syntax, still supported) -->
<!--
  export default {
      mounted()        { ... },
      beforeUnmount()  { ... },
      updated()        { ... },
      errorCaptured()  { ... },
  };
-->

<!-- 9) Suspense — async setup + loading state -->
<!-- Parent -->
<Suspense>
    <template #default>
        <UserProfile :user-id="id" />
    </template>
    <template #fallback>
        <Spinner />
    </template>
</Suspense>

<!-- UserProfile.vue uses top-level await in setup -->
<!--
<script setup>
const { data: user } = await useFetch(`/api/users/${props.userId}`);
</script>
-->

<!-- 10) keep-alive — cache components, preserve state on switch -->
<KeepAlive :include="['Inbox', 'Profile']" :max="5">
    <component :is="currentTab" />
</KeepAlive>

<!-- 11) Real patterns -->

<!-- a) Auto-focus on mount -->
<input ref="input" />
<!-- onMounted(() => input.value.focus()); -->

<!-- b) Fetch + cleanup -->
<!--
const posts = ref([]);
let abort;
onMounted(async () => {
    abort = new AbortController();
    posts.value = await (await fetch('/api/posts', { signal: abort.signal })).json();
});
onBeforeUnmount(() => abort?.abort());
-->

<!-- c) Subscribe to a store / event bus -->
<!--
const unsub = bus.on('refresh', refresh);
onBeforeUnmount(unsub);
-->

<!-- 12) Common bugs -->
<!--
  • Forgetting to unsubscribe in onBeforeUnmount → memory leaks
  • Setting reactive state in onUpdated → infinite update loop
  • Doing async work in setup without try/catch — unhandled rejection
  • Accessing template refs in setup() top-level — they're null until mount
  • Mixing options and composition styles in the same component (works, but confusing)
-->

Why it matters

Reach for watchEffect with onCleanup for any subscription — it’s self-contained and survives reactivity changes. onMounted is for one-shot DOM work; onUnmounted for any leftovers watchEffect didn’t cover.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
onBeforeMount / onMounted / onBeforeUpdate / onUpdated /
onBeforeUnmount / onUnmounted
Try it Yourself »

Discussion

Loading…