All skills
hyf0 avatar

/vue-debug-guides

@a5fc891 official
by hyf0hyf0/vue-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/hyf0/vue-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

No alerts17d5 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 files scanned · No issues

  • 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 hyf0/vue-skills/vue-debug-guides

Guides for diagnosing and fixing Vue 3 runtime errors, reactivity bugs, async failures, and hydration issues across components, templates, watchers, forms, and lifecycle hooks. Covers specific failure modes like stale watchers, ref unwrapping edge cases, v-model sync problems, and SSR mismatches with concrete reference links for each issue.

Generated from the current SKILL.md.

Does this skill cover Vue 2?
No. This skill is specific to Vue 3 debugging and error handling.
What should I use for Vue development best practices instead of debugging?
The skill references a separate `vue-best-practices` skill for development best practices and common gotchas.
Does this cover SSR and hydration issues?
Yes. The skill includes guides for diagnosing and fixing SSR rendering differences and hydration bugs.
Can this help with async watcher and watchEffect problems?
Yes. The skill covers async operation stale data, dependency tracking after await, and proper flush timing for watchers.
Does this address v-model and form-related issues?
Yes. The skill includes guides for v-model initial values, textarea interpolation, IME composition, and custom checkbox form submission.

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