All skills
vuejs-ai avatar

/vue-debug-guides

@a5fc891 official
by Vue AIvuejs-ai/skills2.9k stars
167

Vue 3 debugging and error handling for runtime errors, warnings, async failures, and SSR/hydration issues. Use when diagnosing or fixing Vue issues.

Use this Skill: https://skilld.dev/gh/vuejs-ai/skills/vue-debug-guides

This session only. Nothing lands on disk.

referencets-template-ref-null-handling.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Template Refs Are Null Until Mounted

Impact: HIGH - Template refs have an initial value of null and remain null until the component mounts. They can also become null again if the referenced element is removed by v-if. Always account for this in TypeScript with union types and optional chaining.

Task Checklist

  • Always type template refs with | null union
  • Only access refs inside onMounted or after
  • Use optional chaining (?.) when accessing ref properties
  • Handle v-if scenarios where ref can become null again
  • Consider using useTemplateRef in Vue 3.5+

The Problem

<script setup lang="ts">
import { ref } from 'vue'

// WRONG: Doesn't account for null
const inputRef = ref<HTMLInputElement>()

// WRONG: Will crash if accessed before mount
inputRef.value.focus()  // Error: Cannot read properties of null

// WRONG: Accessed in setup, element doesn't exist yet
console.log(inputRef.value.value)  // Error!
</script>

<template>
  <input ref="inputRef" />
</template>

The Solution

<script setup lang="ts">
import { ref, onMounted } from 'vue'

// CORRECT: Include null in the type
const inputRef = ref<HTMLInputElement | null>(null)

// CORRECT: Access in onMounted when DOM exists
onMounted(() => {
  inputRef.value?.focus()  // Safe with optional chaining
})

// CORRECT: Guard before accessing
function focusInput() {
  if (inputRef.value) {
    inputRef.value.focus()
  }
}
</script>

<template>
  <input ref="inputRef" />
</template>

Vue 3.5+: useTemplateRef

Vue 3.5 introduces useTemplateRef with better type inference:

<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'

// Type is automatically inferred for static refs
const inputRef = useTemplateRef<HTMLInputElement>('input')

onMounted(() => {
  inputRef.value?.focus()
})
</script>

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

Handling v-if Scenarios

Refs can become null when elements are conditionally rendered:

<script setup lang="ts">
import { ref, watch } from 'vue'

const showModal = ref(false)
const modalRef = ref<HTMLDivElement | null>(null)

// WRONG: Assuming ref always exists after first mount
function closeModal() {
  modalRef.value.classList.remove('open')  // May be null!
}

// CORRECT: Always guard access
function closeModal() {
  modalRef.value?.classList.remove('open')
}

// CORRECT: Watch for ref changes
watch(modalRef, (newRef) => {
  if (newRef) {
    // Modal element just mounted
    newRef.focus()
  }
  // If null, modal was unmounted
})
</script>

<template>
  <div v-if="showModal" ref="modalRef" class="modal">
    Modal content
  </div>
</template>

Component Refs

For component refs, use InstanceType:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

// Component ref with null
const childRef = ref<InstanceType<typeof ChildComponent> | null>(null)

onMounted(() => {
  // Access exposed methods/properties
  childRef.value?.exposedMethod()
})
</script>

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

Remember: Child components must use defineExpose to expose methods:

<!-- ChildComponent.vue -->
<script setup lang="ts">
function exposedMethod() {
  console.log('Called from parent')
}

defineExpose({
  exposedMethod
})
</script>

Multiple Refs with v-for

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const items = ref(['a', 'b', 'c'])

// Array of refs for v-for
const itemRefs = ref<(HTMLLIElement | null)[]>([])

onMounted(() => {
  // Access specific item
  itemRefs.value[0]?.focus()

  // Iterate safely
  itemRefs.value.forEach(el => {
    el?.classList.add('mounted')
  })
})
</script>

<template>
  <ul>
    <li
      v-for="(item, index) in items"
      :key="item"
      :ref="el => { itemRefs[index] = el as HTMLLIElement }"
    >
      {{ item }}
    </li>
  </ul>
</template>

Async Operations and Refs

Be careful with async operations:

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const containerRef = ref<HTMLDivElement | null>(null)

onMounted(async () => {
  // containerRef.value exists here

  await fetchData()

  // CAREFUL: Component might have unmounted during await
  // Always re-check before accessing
  if (containerRef.value) {
    containerRef.value.scrollTop = 0
  }
})
</script>

Type Guard Pattern

Create a reusable type guard for cleaner code:

// utils/refs.ts
export function assertRef<T>(
  ref: Ref<T | null>,
  message = 'Ref is not available'
): asserts ref is Ref<T> {
  if (ref.value === null) {
    throw new Error(message)
  }
}

// Usage in component
function mustFocus() {
  assertRef(inputRef, 'Input element not mounted')
  inputRef.value.focus()  // TypeScript knows it's not null here
}

Reference

Source: SKILL.md on GitHub

1 warning17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill is a comprehensive collection of educational debugging guides and best practices for Vue 3 developers. It contains no executable code or malicious instructions and focuses solely on diagnosing common runtime errors and performance issues.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    140/140 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at a5fc891. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 8 months ago

README badge

README badge for vuejs-ai/skills/vue-debug-guides

Guides for diagnosing Vue 3 runtime errors, reactivity bugs, component lifecycle issues, and SSR hydration problems. Covers reactivity traps (refs, computed, watchers), component mechanics (props, emits, slots), templates (v-if, v-for, refs), forms (v-model), and composition API gotchas.

Generated from the current SKILL.md.

Does this cover Vue 2 or only Vue 3?
This skill is for Vue 3 only. It addresses Vue 3-specific debugging patterns like reactivity proxies, script setup, and defineEmits/defineProps.
Does this skill help with performance optimization?
No. This skill focuses on runtime errors, warnings, and behavioral bugs. For development best practices and optimization, use the separate `vue-best-practices` skill.
What kinds of issues does this cover?
Reactivity traps (refs, computed, watchers), component lifecycle bugs, template directives, form binding edge cases, SSR/hydration mismatches, slot scoping, and async-related failures.
Does this include debugging for Nuxt or other Vue frameworks?
No. This skill focuses on core Vue 3 runtime issues. Framework-specific bugs are outside its scope.

Generated from the current SKILL.md. These answers refresh after source changes.