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.

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

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.