Today, you'll explore the life cycle of a component and learn how to effectively utilize it in your Vue applications. This topic guides you through the various stages a Vue component undergoes, from creation to destruction, and shows you how to leverage these stages to control your components' behavior.
What are lifecycle hooks
Every Vue component undergoes a lifecycle, from its creation to its eventual deletion. Vue automatically manages this lifecycle, so there's no need for you to manually control it.
When Vue encounters a new component in your app's template, it triggers the creation and mounting processes. Consequently, the component appears on the user's screen. However, the component's lifecycle doesn't end here.
As you interact with the page, the component's state changes. Vue responds to these changes by re-rendering the component, a process known as the updating phase.
Eventually, you may navigate to a different part of the app, requiring different components to display. When this occurs, Vue removes all inactive components from memory and the DOM tree, a step referred to as the unmounting phase.
The entire lifecycle can be represented by the following diagram:
You could be questioning the importance of understanding these stages if Vue manages them automatically.
The reason is that when developing your own components, you'll frequently need to execute an action at a specific point in the component's lifecycle, like when the component is displayed or removed from the page. This is where lifecycle hooks become useful. They enable you to execute your own code at specific stages of the component lifecycle.
Why we need lifecycle hooks
Consider an illustrative example where lifecycle hooks can be beneficial.
Suppose you want to create a Timer component. Every second, the timer counter should increment by 1. As soon as the component is added to the page, you should start the counter with setInterval, and when it's removed from the page, you should call clearInterval.
You can create methods startCounting and stopCounting to start and stop the counter:
Timer.vue:
<script>
export default {
data() {
return {
seconds: 0,
timerId: null
}
},
methods: {
startCounting() {
this.timerId = setInterval(() => this.seconds++, 1000)
},
stopCountinting() {
clearInterval(this.timerId)
}
}
}
</script>
<template>
<div>Timer: {{ seconds }}</div>
</template>
But how do you ensure these methods are called when an element is added and removed from the page?
This is where lifecycle hooks come in handy:
<script>
export default {
mounted() {
this.startCounting()
console.log("mounted")
},
updated() {
console.log(`updated element: ${this.$el.outerHTML}`)
},
unmounted() {
this.stopCountinting()
console.log("unmounted")
},
data() {
return {
seconds: 0,
timerId: null
}
},
methods: {
startCounting() {
this.timerId = setInterval(() => this.seconds++, 1000)
},
stopCountinting() {
clearInterval(this.timerId)
}
}
}
</script>
<template>
<div>Timer: {{ seconds }}</div>
</template>
In the code above, you've engaged three lifecycle hooks at once.
Using the mounted hook, you can start the counter when the component starts up. To verify if the hook executes, you also output the "mounted" string to the console.
With the unmounted hook, you stop the running timer when the component is removed from the page. For clarity, you also output the "unmounted" string to the console.
The updated hook gets triggered when the component is re-rendered (due to the counter change), and you output the current state of the DOM object of your component to the console.
Indeed, these three hooks are the most commonly used in Vue. Now, let's add a Timer to the page by linking it to the App component and see how your hooks function:
App.vue:
<script>
import Timer from './components/Timer.vue';
export default {
components: {
Timer
}
}
</script>
<template>
<Timer />
</template>
If you launch the application and visit the console, you'll see how the mounted hook functioned. The updated hook is called every time the timer is updated, that is, every second:
To observe how the unmounted hook functions, you need to create a situation where the Timer is removed from the page. For instance, let's create a button in the application that will either remove or add a Timer component to the page:
<script>
import Timer from './components/Timer.vue';
export default {
data() {
return {
showTimer: true
}
},
components: {
Timer
}
}
</script>
<template>
<Timer v-if="showTimer" />
<button @click="showTimer = !showTimer">Show/Hide timer</button>
</template>
Now, if you click the button, the Timer component will disappear from the page, triggering the unmounted hook (and clearing the interval).
When you click the button again, the component will reappear on the page and undergo the creation and mounting stage. The counter will start counting from zero, as the component initializes all its states again.
Exploring lifecycle hooks
You've explored how the three most common hooks function, but for a more comprehensive understanding, it's necessary to examine others.
Let's revisit the lifecycle diagram, but this time, the available hooks are highlighted:
As you can see, Vue provides two versions of callbacks for each lifecycle stage (creation, mounting, updating, and unmounting). One is invoked before the stage, the other after it.
You can set handlers for these hooks in the options object of the component, as demonstrated in the timer example.
<script>
export default {
// ...
beforeCreate() {
},
created() {
},
beforeMount() {
},
mounted() {
},
beforeUpdate() {
},
updated() {
},
beforeUnmount() {
},
unmounted() {
}
// ...
}
</script>
In other words, you can execute some code at any point in the lifecycle. To do this, you simply place it in the corresponding callback. What could be easier!
As previously mentioned, the most commonly used hooks in practice are mounted, updated, and unmounted.
Here's a description of each of the callbacks:
-
beforeCreate – This hook is triggered at the moment the component is initialized. At this stage, the component's
data()and computed properties are not yet accessible. This hook is useful for executing API calls that don't modify the component's data. Any changes made todata()within this hook will be overwritten once the Options API is loaded. -
created – This hook is activated after the instance has completed all state operations. At this point, you can access reactive data, computed properties, methods, and watchers. However,
$el, which represents the component's HTML in Vue, is not yet available as the DOM element hasn't been created. This hook is a good place to make API calls or updatedata(). -
beforeMount – This hook is triggered just before the rendering process begins. The template has been compiled and stored in memory, but it hasn't been attached to the page yet. No DOM elements have been created at this stage, so
$elis still not accessible. -
mounted – This hook is activated when the component is mounted and displayed on the page. Now,
$elis accessible, allowing you to interact with and manipulate the DOM from Vue. This hook will only be triggered after all child components have been fully mounted. It's useful for performing actions on the DOM after it has loaded, such as modifying a specific element. -
beforeUpdate – This hook is triggered just before an update event, which can be caused by a change in
data()or a re-render of the component. This can happen due to a data update in a watcher or a user interaction. After this hook is triggered, the component will re-render and update with the latest data. This hook is useful for accessing the current state of the DOM or even updatingdata()from it. -
updated – This hook is triggered after an update event, once the DOM has been updated to reflect the latest data. It occurs immediately after the re-render. If you access
$elor any other part of the DOM content now, it will show the updated, re-rendered version. If there's a parent component, the child component'supdated()hook is called first, followed by the parent'supdated()hook. -
beforeUnmount – This hook is triggered just before a component is unmounted or removed. At this stage, the DOM elements and other component-related aspects are still accessible. This hook is useful for handling deletion events, such as notifying a server that a user has removed a Node in a table. You still have access to
this.$el, data, watchers, and methods if needed. -
unmounted – This hook is triggered after the component has been completely removed. It can be used to clean up other data, event listeners, or timers to indicate that this component no longer exists on the page. Even at this stage, you still have access to
this.$el, data, watchers, and methods if they're needed.
It's perfectly fine if you don't remember all the hooks immediately. Like any other aspect of programming, learning Vue's lifecycle hooks is a process that comes with practice and experience. You can always refer to the Vue documentation or this topic whenever you need to recall what each hook does. Over time, as you use them more frequently in your projects, you'll start to remember the most commonly used hooks and understand when to use each one.
Now, let's explore best practices and caveats when using lifecycle hooks.
Using beforeCreate and created hooks
As you now understand, to perform an action on the initial start of the component, you need to use the mounted hook. But there are also hooks beforeCreate and created, which are invoked before mounted. When should you use them?
Hooks beforeCreate and created are practical when you need to make requests to the server API.
Certainly, server requests can also be made in the mounted hook, but since beforeCreate and created are initiated earlier than mounted, you'll receive a server response faster (while Vue is rendering HTML, the browser will already be loading data).
However, keep in mind that at the beforeCreate stage, the component states in data() are not yet initialized, so the server response can't be saved there. Therefore, it's sensible to make requests inside beforeCreate that don't require saving the result to the states, for example, sending analytics:
<script>
export default {
beforeCreate() {
fetch("https://analyticsapi.com?component=21").then((result) => {
console.log("analytics about component initialization sent")
})
}
}
</script>
On the other hand, the created hook already has access to the component's states and can be used to load the basic data needed for the component.
For instance, you can load posts from the server and then display them on the page:
App.vue:
<script>
export default {
created() {
fetch("https://jsonplaceholder.typicode.com/posts").then((result) => {
return result.json()
}).then((resultJson) => {
this.posts = resultJson
})
},
data() {
return {
posts: []
}
}
}
</script>
<template>
<div class="posts">
<div v-for="post in posts" class="post">
<div>{{ post.title }}</div>
<div>{{ post.body }}</div>
</div>
</div>
</template>
<style scoped>
.posts {
display: flex;
flex-direction: column;
gap: 10px;
}
.post {
background-color: gray;
border-radius: 10px;
border: 1px solid black;
}
</style>
Result:
The nuance of using the updated hook
Not all hooks are suitable for altering the state of a component. For instance, if you change the state of a component in the updated hook, you'll cause the component to re-render and trigger an endless loop of updated calls.
If, in the Timer example, you tried to increase the counter by one more unit in the updated hook, you'd quickly witness an uncontrolled increase in the counter:
//...
updated() {
this.seconds++
console.log(`updated element: ${this.$el.outerHTML}`)
},
//...
Result (in just a few seconds of running the app):
If you want to change the state of an object when it's updated, you should use the beforeUpdate hook, which is invoked before the DOM of the object is updated. Then, you can achieve the desired effect:
//...
beforeUpdate() {
this.seconds++
console.log(`updated element: ${this.$el.outerHTML}`)
},
//...
Result:
In other words, a state change in beforeUpdate doesn't trigger a new call of beforeUpdate and updated.
Conclusion
In this topic, you learned what lifecycle hooks are in Vue and how to work with them.
As you may remember, the lifecycle of a component consists of four main stages:
-
creation (loading the component options and template)
-
mounting (component rendering in page DOM tree)
-
updating (DOM component update due to state change)
-
unmounting (deleting a component from memory and DOM tree)
Vue provides lifecycle hooks for each of these stages, enabling you to observe or modify the state of your components at specific points in their lifecycle.
The most commonly used hooks are mounted, updated, and unmounted.
The mounted hook is invoked when the component is added to the page (i.e., its DOM is already loaded).
The unmounted hook is called when the component is removed from the page (its DOM has already been removed).
The updated hook is invoked every time a component is re-rendered due to state changes (when its DOM has changed). Also, remember that attempting to change the state in this hook may result in an infinite loop of updated calls.
Now, let's put what we've learned into practice.