Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.2 KiB
5.2 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Template Refs Are Null Until Mounted | HIGH | Accessing template ref before mount or after unmount causes runtime errors | gotcha |
|
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
| nullunion - Only access refs inside
onMountedor after - Use optional chaining (
?.) when accessing ref properties - Handle
v-ifscenarios where ref can become null again - Consider using
useTemplateRefin 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
}