Skip to the solution
MediumFrontend10 min read

What is useTemplateRef() (Vue 3.5) and how does it improve on ref=""?

568 views

The problem

Question presented to candidate: "Vue 3.5 added useTemplateRef(). Before that we wrote const input = ref(null) and ref="input" in the template and it worked. What does useTemplateRef() actually fix, and would you migrate existing code to it?"

What a strong answer should cover:

  • useTemplateRef("key") returns a readonly shallow ref that Vue fills with the element or component whose ref attribute equals "key", matched by a runtime string, not by variable name.
  • The old pattern depended on the compiler matching a variable name to the attribute, which breaks for dynamic refs (:ref="activeKey"): it works against the dev server but stays null in a production build.
  • Because only a string is needed, a composable can own the template ref (useAutofocus("search")), and keys need not be valid JavaScript identifiers.
  • With Vue language tools, the type is inferred from the template: an element ref is typed as that element, a component ref as its exposed instance.
  • Behaviour details: it is null until mount, an array inside v-for, assigning to it is refused, and a second useTemplateRef with the same key warns.
  • Migration: new code should use it; old static refs keep working, but dynamic string refs and refs created inside composables are worth migrating first.

Clarifying questions expected:

  • "Are any of our refs dynamic, or created inside composables?"
  • "Are we on Vue 3.5 and current Vue language tools?"

Code / implementation expected: Yes β€” useTemplateRef in a component, ideally inside a small composable, and an example of a dynamic ref key.

  • usetemplateref
  • refs
  • vue-3.5

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 10 sections

Read the code

VueA composable that owns a template ref, plus a dynamic :ref key that swaps targets
<script setup>
import { ref, useTemplateRef, onMounted } from "vue";

// A composable can own a template ref: all it needs is the key string.
function useAutofocus(key) {
  const el = useTemplateRef(key);
  onMounted(() => el.value?.focus());
  return el;
}

const search = useAutofocus("search-box");   // the key does not have to be a valid JS name

// Dynamic refs: the key can change at runtime, and each useTemplateRef follows its own key.
const active = ref("first");
const first = useTemplateRef("first");
const second = useTemplateRef("second");
const report = ref("");

function swap() {
  active.value = active.value === "first" ? "second" : "first";
}
function check() {
  report.value =
    `focused search: ${document.activeElement === search.value} | ` +
    `first=${first.value?.tagName ?? null} second=${second.value?.tagName ?? null}`;
}
</script>

<template>
  <input ref="search-box" placeholder="search" />
  <input :ref="active" placeholder="dynamic ref" />
  <button class="swap" @click="swap">swap key</button>
  <button class="check" @click="check">check</button>
  <p>{{ report }}</p>
</template>
VueChild.vue and a typed parent: vue-tsc infers each ref from the template
<!-- Child.vue -->
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
function reset() { count.value = 0; }
defineExpose({ reset });
</script>
<template><button @click="count++">{{ count }}</button></template>

<!-- Refs.vue: the parent, checked with vue-tsc -->
<script setup lang="ts">
import { ref, useTemplateRef, onMounted } from "vue";
import Child from "./Child.vue";

const auto = useTemplateRef("search");        // type inferred from the template
const child = useTemplateRef("child");
const manual = ref<HTMLInputElement | null>(null);

onMounted(() => {
  auto.value?.select();                 // fine: auto is HTMLInputElement
  auto.value?.notAMethod();             // error: not on HTMLInputElement
  child.value?.reset();                 // fine: reset is exposed
  child.value?.count;                   // error: count is not exposed
});
</script>
<template>
  <input ref="search" />
  <input ref="manual" />
  <Child ref="child" />
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 47 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

Why are composables preferred over mixins?

Continue