Vue 3 Fragment Image

How to Access a Child Component’s Ref with multi-root node (Fragment) in Vue 3

In this article we want to explain how you can access to your child component's ref if you have more than one root (Fragment).

6 min read

November 18th, 2024

Nick Mousavi

Introduction

If you're used to Vue 2, you might remember that every component's template needed a single root element. In Vue 3, that's no longer necessary because of fragments. This means your components can now have multiple root elements without needing a wrapper.

Component.vue

<!-- Vue 2 -->
<template>
  <div> <!-- wrapper 😫 -->
    <h1>My Blog Post</h1>
    <ArticleComponent>{{ content }}</ArticleComponent>
  </div>
</template>

<!-- Vue 3 -->
<template>
  <h1>My Blog Post</h1>
  <ArticleComponent>{{ content }}</ArticleComponent>
</template>

That's very similar to

in React. However, Vue handles fragments behind the scenes. In fact, in Vue 3, you can think of the
<template>
tag as a fragment.

The
ref()
Problem

In Vue 2, we could easily set a

ref
on a child component, and it would refer to both the wrapper element and the component instance.

But in Vue 3, when there’s no wrapper element, what does the

ref
refer to? 🤔

If the child component uses the

Options API
or doesn't use
<script setup>
, the ref will point to the child component's this, giving the parent full access to its properties and methods.

What if we use

<script setup>
?

Components using

<script setup>
are private by default. To expose properties, we need to use the
defineExpose
macro.

Access To Children's Element

This is what happen when you have wrapper (single root) element:

Wrapper.vue
<!-- Child -->
<template>
  <div class="wrapper"> <!-- Root -->
    <h1>My Blog Post</h1>
    <ArticleComponent>{{ content }}</ArticleComponent>
  </div>
</template>



<!-- Parent -->
<script setup lang="ts">
const childRef = ref()

onMounted(()=>{
  console.log(childRef.value.$el); // <div class="wrapper">…</div>})
</script>

<template>
  <Child ref="childRef" />
</template>

And when you have more than one root:

NoWrapper.vue
<!-- Child -->
<template>
  <h1>My Blog Post</h1> <!-- Root 1 -->
  <ArticleComponent>{{ content }}</ArticleComponent> <!-- Root 2 -->
</template>



<!-- Parent -->
<script setup lang="ts">
const childRef = ref()

onMounted(()=>{
  console.log(childRef.value.$el); // #text})
</script>

<template>
  <Child ref="childRef" />
</template>
Wait, what, what happened?

When we using Fragment(multiple nodes), Vue creates a

node that wraps our child component root nodes.

When using Fragments in Vue 3, Vue inserts an empty text node at the beginning of the component as a marker, which is why $el returns a

node.

#text
is like a reference point that Vue uses internally.

Also I should mention that you have still access to component instance (if you don't use

<script setup>
in child).

Solution

  1. Use Single Root Like this
  2. Use Template Refs + defineExpose

Using Template Refs + defineExpose

TemplateRef.vue
<!-- Child -->
<script setup lang="ts">
import { ref } from 'vue';

const h1Ref = ref()
const articleRef = ref()


defineExpose({
  h1Ref,
  articleRef
})
</script>

<template>
  <h1 ref="h1Ref">My Blog Post</h1> 
  <ArticleComponent ref="articleRef">{{ content }}</ArticleComponent> 
</template>



<!-- Parent -->
<script setup lang="ts">
const childRef = ref()

onMounted(()=>{
  console.log(childRef.value);    //  {h1Ref: RefImpl, articleRef: RefImpl}
})
</script>

<template>
  <Child ref="childRef" />
</template>

Now you have access to your

refs
and all the things that you exposed by using
defineExpose
.

7 key questions and answers about template refs

A ref in Vue is a way to directly access DOM elements or child component instances. It provides a reference that you can use to manipulate elements, call methods, or access component data programmatically.

Vue 2 requires a single root element in every component (must wrap everything in one element), while Vue 3 supports fragments, allowing multiple root nodes without a wrapper element.

<script setup>

In Vue 2, refs are accessed via

this.$refs.myRef
. In Vue 3 with
<script setup>
, you need to:

  • Declare the ref using
    const myRef = ref(null)
  • Access it using
    myRef.value
  • Use
    defineExpose
    if you want to make refs available to parent components.
refs
on a child component, what does
$el
refer to in Vue 2 vs Vue 3?

In Vue 2,

$el
refers to the root DOM element of the component (since there must be one). In Vue 3 with fragments,
$el
points to an empty text node that Vue creates as a marker, since there might be multiple root elements.

<script setup>
in Vue 3, what extra step is needed to make
refs
accessible to parent components?

When using

<script setup>
, you must explicitly use
defineExpose
to make any
refs
, methods, or properties available to parent components. Without this, parent components cannot access the child's internal
refs
.

  • Use a single root element if you just need simple DOM access
  • Use template refs with defineExpose when you need access to specific elements, making them explicitly available to the parent component
ref
and
useTemplateRef
for template references in Vue 3.5+?

Both can be used for template references, but they have different approaches and benefits:

  1. Using ref (before Vue 3.5):
<script setup>
const input = ref(null)  // name must match template ref
</script>

<template>
  <input ref="input" />
</template>
  1. Using useTemplateRef (Vue 3.5+):
<script setup>
const input = useTemplateRef('my-input')  // first argument matches ref value
</script>

<template>
  <input ref="my-input" />
</template>

Key differences:

Resources and References

Vue Template Refs

Vue Fragment

Tags:Vue