Sometimes Vue's reactivity system isn't enough, and you just need to re-render a component.
Or maybe you just want to blow away the current DOM and start over.
So how do you get Vue to reload a component the right way?
The best way to force Vue to re-render a component is to set a :key
on the component. When you need the component to be re-rendered, you just change the value of the key and Vue will re-render the component.
This is called the Key-Changing Technique, and it's a pretty simple solution, right?
In this article we’ll look at a few other ways to solve this problem that work in both Vue 2 and Vue 3:
However, if you need to force a reload or force an update, there's probably a better way.
It's likely that you're misunderstanding one of the following tricky things:
:key
attribute with v-for
Now, there are valid use cases for forcing an update. Most of these will be solved using the Key-Changing Technique that's at the bottom of this article.
Let’s get started, shall we?
This is equivalent to restarting your computer every time you want to close an app.
I guess it would work some of the time, but it's a pretty bad solution.
There isn't really much more to say about this. Don't do it!
Let's look for a better way.
A slightly better solution is to get clever with our use of the v-if
directive.
In your template
you'll add the v-if
directive on the component you want to reload:
<template><MyComponent v-if="renderComponent" /></template>
In your script setup
you'll add in the forceRerender
method that uses nextTick
:
import { nextTick, ref } from 'vue';const renderComponent = ref(true);const forceRerender = async () => {// Remove MyComponent from the DOMrenderComponent.value = false;// Wait for the change to get flushed to the DOMawait nextTick();// Add the component back inrenderComponent.value = true;};
If you’re using the Options API instead of the Composition API, it will look like this:
export default {data() {return {renderComponent: true,};},methods: {async forceRerender() {// Remove MyComponent from the DOMthis.renderComponent = false;// Wait for the change to get flushed to the DOMawait this.$nextTick();// Add the component back inthis.renderComponent = true;}}};
This is what's going on here:
renderComponent
is set to true
, so MyComponent
is renderedforceRerender
we immediately set renderComponent
to false
MyComponent
because the v-if
directive now evaluates to false
renderComponent
back to true
v-if
directive evaluates to true
again, so we start rendering a refreshed instance of MyComponent
There are two pieces that are important in understanding how this works.
First, we have to wait until the next tick or we won't see any changes.
In Vue, a tick is a single DOM update cycle. Vue will collect all updates made in the same tick, and at the end of a tick it will update what is rendered into the DOM based on these updates.
If we don't wait until the next tick, our updates to renderComponent
will just cancel themselves out, and nothing will change.
Second, Vue will create an entirely new and refreshed component when we render the second time. This is because Vue will destroy the first one and create a new one. This means that our new MyComponent
will go through all of its lifecycles as normal — created
, mounted
, and so on.
This works, but it isn't a great solution. I call it a hack because we're hacking around what Vue wants us to do.
So instead, let's do what Vue wants us to do!
This is one of the two best ways to solve this problem, both of which are officially supported by Vue.
Normally, Vue will react to changes in dependencies by updating the view. However, when you call forceUpdate
, you can force that update to occur, even if none of the dependencies has actually changed.
This circumvents the entire reactivity system, which is why it’s not recommended as a solution.
But sometimes, Vue's reactivity system can be confusing, and we think that Vue will react to changes to a certain property or variable, but it doesn't.
If you’re using Vue 2, there are certain cases where Vue's reactivity system won't detect any changes at all. But Vue 3 has a much more robust proxy-based reactivity system that doesn’t suffer from these same issues.
Again, if you need to force your component to re-render or reload, there's probably a better way. You’re likely just treating the symptom and not the actual underlying issue.
Here’s how you can call forceUpdate
using the Options API:
export default {methods: {methodThatForcesUpdate() {// ...this.$forceUpdate(); // Notice we have to use a $ here// ...}}}
In order to call it using the Composition API in Vue 3, we have to get a little clever since this method only lives on the component instance:
import { getCurrentInstance } from 'vue';const methodThatForcesUpdate = () => {// ...const instance = getCurrentInstance();instance.proxy.forceUpdate();// ...};
Important: This will not update any computed properties you have. Calling forceUpdate
will only force the view to re-render.
There are many cases where you will have a legitimate need to force refresh a component.
To do this the proper way, we will supply a key
attribute so Vue knows that a specific component is tied to a specific piece of data. If the key stays the same, it won't change the component, but if the key does change, Vue knows that it should get rid of the old component and create a new one.
Exactly what we need!
First, I’ll explain the Key-Changing Technique. Then, I’ll take a moment to explain why we use the key
attribute in Vue.
Finally, here is the very best way (in my opinion) to force Vue to refresh a component.
We add a key
attribute to our component, and then change that key whenever we need the component to be re-rendered.
Here is a very basic way of doing it with script setup
:
<template><MyComponent :key="componentKey" /></template>
import { ref } from 'vue';const componentKey = ref(0);const forceRerender = () => {componentKey.value += 1;};
Here’s how you’d do it with the Options API if you’re not using Vue 3 or the Composition API:
export default {data() {return {componentKey: 0,};},methods: {forceRerender() {this.componentKey += 1;}}}
Every time that forceRerender
is called, the value of componentKey
will change. When this happens, Vue will know that it has to destroy the component and create a new one.
What you get is a child component that will re-initialize itself and "reset" it's state, forcing a refresh and re-render of the component.
A simple and elegant way to solve our problem!
I write about this key-changing technique in more detail here.
We can dig a little deeper into what this key
attribute is actually doing here, and why we need it.
Let's say you're rendering a list of components that has one or more of the following:
setup
function with Vue 3 or in the created
and mounted
hooks if using the Options APIIf you sort that list, or update it in any other way, you'll need to re-render parts of the list. But you won't want to re-render everything in the list, just the things that have changed.
To help Vue keep track of what has changed and what hasn't, we supply a key
attribute. Using the index of an array is not helpful here, since the index is not tied to specific objects in our list.
Here is an example list that we have:
const people = [{ name: 'Evan', age: 34 },{ name: 'Sarah', age: 98 },{ name: 'James', age: 45 },];
If we render it out using indexes we will get this:
<ul><li v-for="(person, index) in people" :key="index">{{ person.name }} - {{ index }}</li></ul>
Evan - 0Sarah - 1James - 2
If we remove Sarah, we will get:
Evan - 0James - 1
The index associated with James is changed, even though James is still James. James will be re-rendered, even if we don't want him to be.
We want to use some sort of unique id, however we end up generating it:
const people = [{ id: 'this-is-an-id', name: 'Evan', age: 34 },{ id: 'unique-id', name: 'Sarah', age: 98 },{ id: 'another-unique-id', name: 'James', age: 45 },];
<ul><li v-for="person in people" :key="person.id">{{ person.name }} - {{ person.id }}</li></ul>
Before when we removed Sarah from our list, Vue deleted the components for Sarah and James, and then created a new component for James. Now, Vue knows that it can keep the two components for Evan and James, and all it has to do is delete Sarah's.
But it gets even better.
If we add a person to the list, it also knows that it can keep all of the existing components, and it only has to create a single new component and insert it into the correct place. This is really useful, and helps us a lot when we have more complex components that have their own state, have initialization logic, or do any sort of DOM manipulation.
Just remember, if you find yourself needing to force Vue to re-render a component, there’s probably a better way.
If, however, you do need to re-render something, choose the Key-Changing Technique over anything else.