How do I call a method of a child component?
Calling a child method is rarely something that you should need to do in Vue. We often see this question asked on Vue Land, but once we dig a bit further it usually transpires that invoking a child component's method isn't actually the best way to solve the underlying problem. This is known as an XY problem.
If you're trying to call a child method, you should first consider whether the problem would be better solved via props or v-model
instead, or by moving the relevant responsibility out of the child component altogether. We'll explore some of those alternatives later.
But there are valid use cases for invoking a child method. In the examples below we'll use a method to focus an element within the child.
Examples
Here are 3 Playgrounds that show a child method being invoked. They are essentially the same example but written using different APIs:
You can see the full code for those examples in the Playgrounds, but just in case you can't access the Playgrounds, here's the code for the <script setup>
example:
<script setup>
import { useTemplateRef } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = useTemplateRef('myChild')
function onButtonClick() {
childRef.value.focusInput()
}
</script>
<template>
<ChildComponent ref="myChild" />
<button @click="onButtonClick">Focus</button>
</template>
<script setup>
import { useTemplateRef } from 'vue'
const el = useTemplateRef('inputEl')
function focusInput() {
el.value.focus()
}
defineExpose({
focusInput
})
</script>
<template>
<input ref="inputEl">
</template>
We'll explain those examples shortly, but we aren't going to repeat everything that's in the documentation. The relevant page in the official documentation is here:
If you're struggling to get this working in your own code then you should start by reading that page carefully.
We also have a separate FAQ entry that might be helpful:
Template refs
The main concept that we use is known as a template ref.
The examples above actually use two template refs, one in the parent and one in the child. In the child, we use a template ref to grab a reference to the <input>
element. In the parent, we use a template ref to get a reference to the child component instance. These two template refs are independent of each other, but we need both to implement the focus functionality.
To create a template ref, we first need to add a ref
attribute in the template:
<ChildComponent ref="myChild" />
Here, myChild
is an arbitrary name that we've given the template ref.
With the Composition API, we then use useTemplateRef
to access the ref:
const childRef = useTemplateRef('myChild')
With the Options API, the template ref is accessible via this.$refs
:
this.$refs.myChild
Template refs won't be populated until after rendering. In the example above this doesn't matter, as the button can't be clicked until everything is rendered.
Exposing child methods
Using a template ref gives us a reference to the child component instance, but that doesn't necessarily allow us to invoke its methods:
- If the child is using
<script setup>
, methods are not exposed by default. You need to use thedefineExpose
macro to expose any methods in the child that you want to access externally. - If you're using an explicit
setup
function, you'll need to return the method fromsetup
, even if it isn't used in the child's own template. Anything returned fromsetup
will be accessible via the template ref, unless you explicitly opt out by using theexpose
option. - If the child is using the Options API, any method in the child's
methods
section will be accessible via the template ref, unless you're using theexpose
option to opt out.
Debugging problems
If something isn't working, it can be helpful to use console logging to check the value of the template ref. But any logging needs to be interpreted carefully.
If you're using the Composition API, make sure you log the ref's value, not the ref itself:
// This isn't reliable:
console.log(childRef)
// Do this instead:
console.log(childRef.value)
The problem with using console.log(childRef)
is that the value of the ref could change after it's logged. When you expand the ref in the console you'll see the current value of the ref, not the value it had at the point it was logged. In some cases it might be useful to log both and compare them.
Similarly, with the Options API, it's better to log a specific ref, rather than the whole of this.$refs
:
// This is more reliable
console.log(this.$refs.myChild)
// But logging both is fine if you understand the problems
console.log(this.$refs)
Again, when you expand this.$refs
in the console you'll see the current properties of that object, not the properties it had when it was logged.
Once you've logged the value, you should see a Proxy
object, which is used to represent the component instance. You can dig down further into the [[Target]]
property (and any nested proxies) to see what methods the instance exposes.
If the logged value is undefined
, the template ref is not being populated. There are two likely causes: either the name doesn't match, or you're trying to access the ref too soon. It won't be populated until after rendering is complete.
Also check that the logged value isn't an array. If you use a template ref inside a v-for
then the value will be wrapped in an array, even if there's only one instance of the component. You'll need to use childRef.value[0]
or this.$refs.myChild[0]
in that case to read out the first entry.
That array can also lead to misleading logging. Like with the earlier examples, expanding the array in the console will show the items currently in the array, which may differ from the items that were present when it was logged. The template ref will update the same array when the component re-renders, it doesn't create a new array each time.
Using props instead
Consider this child component, which uses defineExpose
to expose a method called setCount
:
<script setup>
import { ref } from 'vue'
const count = ref()
// Don't do this!
defineExpose({
setCount(newCount) {
count.value = newCount
}
})
</script>
<template>
<div>{{ count ?? '?' }}</div>
</template>
It does work, but it would be considered highly unusual to implement it that way in Vue.
The normal way to do this is via props:
<script setup>
defineProps({
count: Number
})
</script>
<template>
<div>{{ count ?? '?' }}</div>
</template>
This moves the data into the parent, which then passes it down via a prop.
If the child also needs to be able to modify the data then it should emit an event to the parent, telling the parent to update the data. Combining a prop and event like this is usually implemented using v-model
.
Using v-model
instead
This example is similar to the previous example, but it creates a two-way binding for count
via defineModel
. This allows both the child and parent to update the value of count
:
<script setup>
const count = defineModel('count', { type: Number })
function reset() {
// This will emit an event to the parent
count.value = undefined
}
</script>
<template>
<div class="count-box">{{ count ?? '?' }}</div>
<button @click="reset">
Reset
</button>
</template>