Lifecycle Hooks
Vue.js Development
Lifecycle Hooks
Every Vue component goes through a series of initialization steps when it is created — setting up reactive data, compiling the template, mounting the instance to the DOM, and updating the DOM when data changes. Vue exposes lifecycle hooks that let you run code at specific stages of this process. Common use cases include fetching data when a component mounts, setting up event listeners or timers, cleaning up resources when a component is destroyed, and reacting to DOM updates. Understanding the lifecycle helps you write efficient components that correctly manage side effects.
Lifecycle Diagram
The main lifecycle stages are: creation (setup/beforeCreate/created), mounting (beforeMount/mounted), updating (beforeUpdate/updated), and unmounting (beforeUnmount/unmounted). In the Composition API, these map to onMounted, onUpdated, onUnmounted, and their "before" counterparts.
Options API Hooks
<script>
export default {
data() {
return {
users: [],
timer: null,
};
},
// Called after instance is created, before mounting
created() {
console.log("Component created — data is reactive");
// Good for: initial API calls, setting up non-DOM logic
this.fetchUsers();
},
// Called after component is mounted to the DOM
mounted() {
console.log("Component mounted — DOM is available");
// Good for: DOM manipulation, third-party library init, timers
this.timer = setInterval(() => {
this.fetchUsers();
}, 30000);
// Access DOM elements via $refs
this.$refs.searchInput?.focus();
},
// Called after reactive data change causes DOM update
updated() {
console.log("DOM updated after data change");
// Good for: post-update DOM operations
// Caution: avoid changing state here (infinite loop risk)
},
// Called before component is unmounted
beforeUnmount() {
console.log("About to unmount — clean up!");
// Good for: clearing timers, removing event listeners
clearInterval(this.timer);
},
methods: {
async fetchUsers() {
const response = await fetch("/api/users");
this.users = await response.json();
},
},
};
</script>
Composition API Hooks
<script setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
const users = ref([]);
const searchInput = ref(null); // template ref
let timer = null;
async function fetchUsers() {
const response = await fetch("/api/users");
users.value = await response.json();
}
onBeforeMount(() => {
console.log("Before mount — DOM not yet available");
});
onMounted(() => {
console.log("Mounted — DOM is ready");
fetchUsers();
// Start polling
timer = setInterval(fetchUsers, 30000);
// Focus the search input
searchInput.value?.focus();
});
onBeforeUpdate(() => {
console.log("Before DOM update");
});
onUpdated(() => {
console.log("DOM updated");
});
onBeforeUnmount(() => {
console.log("Before unmount — last chance to clean up");
});
onUnmounted(() => {
console.log("Unmounted — component destroyed");
clearInterval(timer);
});
</script>
<template>
<div>
<input ref="searchInput" placeholder="Search users..." />
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>
Practical Lifecycle Patterns
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
// Pattern 1: Event listener cleanup
const windowWidth = ref(window.innerWidth);
function handleResize() {
windowWidth.value = window.innerWidth;
}
onMounted(() => window.addEventListener("resize", handleResize));
onUnmounted(() => window.removeEventListener("resize", handleResize));
// Pattern 2: Third-party library integration
let chartInstance = null;
const chartContainer = ref(null);
onMounted(() => {
// Initialize chart library after DOM is ready
// chartInstance = new Chart(chartContainer.value, { ... });
});
onUnmounted(() => {
// Destroy chart to prevent memory leaks
chartInstance?.destroy();
});
// Pattern 3: AbortController for fetch cleanup
const data = ref(null);
let abortController = null;
onMounted(async () => {
abortController = new AbortController();
try {
const response = await fetch("/api/data", {
signal: abortController.signal,
});
data.value = await response.json();
} catch (e) {
if (e.name !== "AbortError") console.error(e);
}
});
onUnmounted(() => {
abortController?.abort(); // Cancel in-flight request
});
</script>
Tip: Always clean up side effects inonUnmounted(orbeforeUnmount). This includes clearing intervals and timeouts, removing event listeners, aborting fetch requests, and destroying third-party library instances. Failing to clean up causes memory leaks.
Key Takeaways
onMountedruns after the component's DOM is ready — use it for DOM access and API calls.onUnmountedruns when the component is destroyed — use it to clean up timers, listeners, and subscriptions.onUpdatedruns after DOM updates — avoid changing state inside to prevent infinite loops.- In the Composition API, lifecycle hooks are imported functions called inside
setupor<script setup>. - Always pair setup with cleanup:
addEventListenerwithremoveEventListener,setIntervalwithclearInterval.