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.

referenceprovide-inject-default-value-factory.md

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

Use Factory Functions for Non-Primitive Inject Default Values

Impact: MEDIUM - When providing default values for inject(), using an object literal creates a single shared reference. All components using that default will share the same object, leading to unexpected state sharing and bugs.

Task Checklist

  • Always use factory functions for object/array default values in inject
  • Pass true as the third argument to enable factory mode in Composition API
  • Use the object syntax with factory function in Options API
  • Only use literal defaults for primitive values (strings, numbers, booleans)

The Gotcha: Shared Default References

Wrong - Object literal creates shared reference:

<script setup>
import { inject } from 'vue'

// WRONG: All components without a provider share this SAME object
const config = inject('config', { debug: false, apiUrl: '' })

// If one component does this:
config.debug = true

// ALL other components using this default now have debug: true!
</script>

Correct - Factory function creates unique instance:

<script setup>
import { inject } from 'vue'

// CORRECT: Each component gets its own object
// Third argument `true` indicates the second arg is a factory function
const config = inject('config', () => ({ debug: false, apiUrl: '' }), true)
</script>

API Explanation

The inject() function has multiple signatures:

// Simple default value (OK for primitives)
inject(key, defaultValue)

// Factory function for non-primitives (REQUIRED for objects/arrays)
inject(key, factoryFunction, true)

The third argument true tells Vue that the second argument is a factory function, not the default value itself.

Examples

Primitive Defaults (No Factory Needed)

<script setup>
import { inject } from 'vue'

// Primitives are safe without factory
const count = inject('count', 0)
const name = inject('name', 'Guest')
const enabled = inject('enabled', false)
</script>

Object Defaults (Factory Required)

<script setup>
import { inject } from 'vue'

// Objects MUST use factory
const user = inject('user', () => ({
  id: null,
  name: 'Anonymous',
  preferences: {}
}), true)

const settings = inject('settings', () => ({
  theme: 'light',
  language: 'en',
  notifications: true
}), true)
</script>

Array Defaults (Factory Required)

<script setup>
import { inject } from 'vue'

// Arrays MUST use factory
const items = inject('items', () => [], true)
const permissions = inject('permissions', () => ['read'], true)
</script>

Class Instance Defaults (Factory Required)

<script setup>
import { inject } from 'vue'
import { Logger } from '@/utils/logger'

// Class instances MUST use factory
const logger = inject('logger', () => new Logger({ level: 'warn' }), true)
</script>

Options API Syntax

In Options API, use the object syntax with a default factory function:

export default {
  inject: {
    // Primitive - can use literal
    theme: {
      from: 'theme',
      default: 'light'
    },

    // Object - MUST use factory
    config: {
      from: 'config',
      default: () => ({ debug: false })
    },

    // Array - MUST use factory
    permissions: {
      from: 'permissions',
      default: () => []
    }
  }
}

Real-World Example: Form Context

<!-- FormProvider.vue -->
<script setup>
import { provide, reactive } from 'vue'

const formContext = reactive({
  values: {},
  errors: {},
  touched: {},
  isSubmitting: false
})

provide('formContext', formContext)
</script>

<!-- FormField.vue (might be used outside FormProvider) -->
<script setup>
import { inject } from 'vue'

// Safe default that won't be shared
const formContext = inject('formContext', () => ({
  values: {},
  errors: {},
  touched: {},
  isSubmitting: false,
  // Mark as standalone mode
  isStandalone: true
}), true)

// Component works both inside and outside FormProvider
</script>

TypeScript: Typing Factory Defaults

import { inject } from 'vue'
import type { InjectionKey } from 'vue'

interface Config {
  apiUrl: string
  debug: boolean
  features: string[]
}

const ConfigKey: InjectionKey<Config> = Symbol('config')

// TypeScript understands the factory return type
const config = inject(ConfigKey, () => ({
  apiUrl: 'https://api.example.com',
  debug: false,
  features: []
}), true)

Common Mistake in Testing

This gotcha often appears in tests where components are rendered without providers:

// test.spec.ts
import { mount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'

// Without provider, all test instances share the wrong default
it('test 1', () => {
  const wrapper = mount(MyComponent)
  wrapper.vm.config.debug = true // Pollutes other tests!
})

it('test 2', () => {
  const wrapper = mount(MyComponent)
  // Might fail because debug is still true from test 1
})

Fix: Use factory functions in the component, or provide in tests:

it('test with provider', () => {
  const wrapper = mount(MyComponent, {
    global: {
      provide: {
        config: { debug: false, apiUrl: '' }
      }
    }
  })
})

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.