Vue.js 3 Lifecycle Hooks Tutorial

In this Vue tutorial we learn the steps Vue follows to render an application and how we can execute code at each phase.

We cover the different phases and the methods we can use to hook into them as well as the lifecycle of nested components.

Lesson Project

If you want to follow along with the examples in this lesson, you will need an app generated by the Vue CLI . If you already have one from the previous lesson, you can use it instead.

In the root App component, we’ll have a data property that’s displayed in a heading in the template.

Example: src/App.vue
<template>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  data() {
    return { msg: '' }
  }
}
</script>

The Vue instance lifecycle

Vue has an elaborate lifecycle and follows certain steps to render an application. Vue also allows us to execute our own code during any one of these steps by “hooking” into the lifecycle process.

The Vue lifecycle can be broken down into 4 phases.

  1. Creation
  2. Mounting
  3. Updating
  4. Unmounting

These phases each have two predefined options that we can use in the configuration object. Each option requires a function as its value.

Example: configuration object
export default {
  optionName: function() {

  }
}

We can use the ES6 shorthand syntax and specify the option as a function.

Syntax: ES6
export default {
  optionName() {

  }
}

In these options we specify the code we want to execute for each step in the lifecycle process.

Let’s go through the lifecycle and then the order in which the hooks are invoked.

The lifecycle starts in the main.js file, when we create a new Vue app with the createApp method.

1. Creation Phase: Before Create

In the creation phase, the first step Vue reaches is the Before Create step.

This step uses the beforeCreate lifecycle hook and will be invoked before the app has fully initialized.

To demonstrate, let’s start by adding the beforeCreate hook to our example. Inside it, we’ll change the data property’s value and log it to the console.

Example: src/App.vue
<template>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  data() {
    return { msg: '' }
  },
  beforeCreate() {
    this.msg = 'Hello'
    console.log(this.msg)
  }
}
</script>

If we save the file and take a look in the browser, nothing shows on the page.

That’s because at this point, Vue is just aware of the app configuration and things like data properties, computed properties, methods, watchers etc.

If we take a look in the browser’s console, we see that msg does actually have the new value we gave it. So it is aware of msg and even that we changed it, but it hasn’t compiled the template block yet so even if we add a value to msg , we won’t see anything on the screen.

2. Creation Phase: Created

The second step in the creation phase is the Created step.

This step uses the created lifecycle hook. The hook method will be invoked after the app has fully initialized.

This is the time that Vue will compile the template. For example, all the string interpolations are replaced with the concrete values that should be shown to the user.

To demonstrate, let’s change beforeCreate to created in our example.

Example: src/App.vue
<template>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  data() {
    return { msg: '' }
  },
  created() {
    this.msg = 'Hello'
    console.log(this.msg)
  }
}
</script>

This time, the text shows in the browser. But the text doesn’t actually show in this step though.

At this point, Vue only compiles the template.

Example:
<template>
  <p>{{ msg }}</p>
</template>

// was replaced with

<p>Hello</p>

So why do we see the text on the screen then? Well, after the Creation phase comes the Mounting phase which is where the rendering happens.

In the Before Create step, Vue was aware of msg and its value. But the template hadn’t been compiled yet so there was nothing to show in the Mounting phase. In the Created step, Vue compiled the template so there was something to show in the Mounting phase.

The created hook method is a good place to make API calls. For example, we can check if a user is logged in and fetch their details to personalize their experience.

3. Mounting Phase: Before Mount

After the template has been compiled, we reach the Before Mount step.

This step uses the beforeMount lifecycle hook and is invoked right before Vue renders our content in the browser.

To demonstrate, let’s add the beforeMount hook to our config object and change msg to have a new value again.

Example: src/App.vue
<template>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  data() {
    return { msg: '' }
  },
  created() {
    this.msg = 'Hello'
    console.log(this.msg)
  },
  beforeMount() {
    this.msg = 'Hello World'
    console.log(this.msg)
  }
}
</script>

If we save the file and take a look in the browser, we’ll see the text is now “Hello World”.

If we open the browser’s console, we can see the text was first changed to “Hello” in the created method, but then changed to “Hello World” before it was rendered to the page.

4. Mounting Phase: Mounted

The second step in the mounting phase is the Mounted step.

This step uses the mounted lifecycle hook and is invoked when Vue renders our content in the browser.

At this point, our app is shown to the user in all its glory. The DOM is also now ready for access and manipulation so the user can start interacting with our app. When they do, a new lifecycle phase will trigger.

The mounted hook counts as an update. If we change the text, it will trigger the Updating phase.

To demonstrate, let’s add the mounted method to our example and change the text back to “Hello”. We will also add two more hook methods called beforeUpdate and updated with a console log in each.

Example: src/App.vue
<template>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  data() {
    return { msg: '' }
  },
  beforeMount() {
    this.msg = 'Hello World'
    console.log(this.msg)
  },
  mounted() {
    this.msg = 'Hello'
    console.log(this.msg)
  },
  beforeUpdate() {
    console.log('Before Update Triggered')
  },
  updated() {
    console.log('Updated Triggered')
  }
}
</script>

If we take a look in the browser, we will see msg changed back to “Hello”. But in the console, we will also see the two logs that state the Update phase has been triggered.

5. Updating Phase: Before Update

When a user starts changing data, we reach the Before Update step.

This step uses the beforeUpdate lifecycle hook and is invoked after the data changes, but before the DOM is patched (before we see the update on the page).

To demonstrate, let’s add a button to the template block that changes msg to “Hello” when clicked.

In the beforeUpdate method, we’ll change msg to “Hello World”. We’ll also remove all the other hook methods.

Example: src/App.vue
<template>
  <button @click="msg = 'Hello'">Change Message</button>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  data() {
    return { msg: '' }
  },
  beforeUpdate() {
    this.msg = 'Hello World'
  }
}
</script>

If we click the button in the browser, the msg shows “Hello World” even though we specified “Hello” on the button.

What happened is that msg did change to “Hello”, but before that change could be written to the DOM, the beforeUpdate method changed the value to “Hello World”.

The Before Update step is a good place to remove event listeners that are no longer needed.

6. Updating Phase: Updated

After the update is processed, we reach the Updated step.

This step uses the update lifecycle hook method and is invoked when the update has been processed (when we see the update on the page).

Note: The template doesn’t unmount when the user changes data. It’s only a rerender, so there is no mounting or unmounting phase.

7. Unmounting Phase: Before Unmount

Sometimes we may need to remove our app, for whatever reason, from the page. That’s when we reach the Before Unmount step.

This step uses the beforeUnmount lifecycle hook and is invoked right before the app is unmounted (before we stop seeing the app in the browser).

At this point, the app is still fully functional. We can perform any necessary cleanup like cancelling network requests or invalidating timers.

8. Unmounting Phase: Unmounted

After the app has been unmounted, we reach the Unmounted step.

This step uses the unmounted lifecycle hook and is invoked when the update has been processed (when we stop seeing the app in the browser).

The lifecycle of nested components

We’ve discussed the Vue instance lifecycle throughout the lesson. But it’s not really the application’s lifecycle, it’s the root App component’s lifecycle. All the components in our application has such a lifecycle.

For example, let’s say we have a nested child component that we show/don’t show based on a button click.

Once we click the button to show the nested child component, it will go through the Creation and Mounting lifecycle phases. If there is something inside it that causes a change in data, it will go through the Updating lifecycle phase. And if we click the button to hide it, it will go through the Unmounting phase.

To demonstrate, create a new component called ChildHooks.vue .

Example: project
project-folder/
├── src/
|   ├── components/
|   |   └── ChildHooks.vue
|   └── App.vue

Inside it, we’ll add hooks for the Creation, Mounting and Unmounting phases with a console log for each. The console logs are prefixed with the word “Child” to help us identify which component is affected.

Example: src/components/ChildHooks.vue
<template>
  <p>Child Component</p>
</template>

<script>
export default {
  beforeCreate()  { console.log('Child beforeCreate()') },
  created()       { console.log('Child created()')      },
  beforeMount()   { console.log('Child beforeMount()')  },
  mounted()       { console.log('Child mounted()')      },
  beforeUnmount() { console.log('Child beforeUnmount()')},
  unmounted()     { console.log('Child unmounted()')    }
}
</script>

In the root App component, we’ll import and use the new component on a toggle button. We’ll also add hooks for the Creation, Mounting and Updating phases with a console log for each. The console logs are prefixed with the word “Parent” to help us identify which component is affected.

Example: src/App.vue
<template>
  <button @click="showChild = !showChild">
    Toggle Child Component
  </button>

  <child-hooks v-if="showChild" />
</template>

<script>
import ChildHooks from './components/ChildHooks.vue'

export default {
  components: { ChildHooks },
  data() {
    return { showChild: false }
  },
  beforeCreate() { console.log('Parent beforeCreate()')},
  created()      { console.log('Parent created()')     },
  beforeMount()  { console.log('Parent beforeMount()') },
  mounted()      { console.log('Parent mounted()')     },
  beforeUpdate() { console.log('Parent beforeUpdate()')},
  updated()      { console.log('Parent updated()')     }
}
</script>

Without clicking the button, we should see the following logs in the browser’s console.

Output:
Parent beforeCreate()
Parent created()
Parent beforeMount()
Parent mounted()

So the root App component was created and mounted as expected.

If we click on the button, the child component shows and the following logs are added.

Output:
Parent beforeUpdate()

Child beforeCreate()
Child created()
Child beforeMount()
Child mounted()

Parent updated()

A button click causes an update on the parent component, so the beforeUpdate hook is called. In this step, Vue will create and mount the child component.

Once the child is mounted, the parent is ready to add it to the DOM with its updated hook.

If we click the button again, the child component is not shown anymore and the following logs are added.

Output:
Parent beforeUpdate()

Child beforeUnmount()
Child unmounted()

Parent updated()

The button click invokes the beforeUpdate hook again. But this time, the child component’s unmounting phase triggers.

Once the child is unmounted, the parent is ready to remove it from the DOM in its updated hook.