--- title: Use Factory Functions for Non-Primitive Inject Default Values impact: MEDIUM impactDescription: Using object literals as default values creates shared references across all consuming components type: gotcha tags: [vue3, provide-inject, composition-api, memory, shared-state] --- # 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:** ```vue ``` **Correct - Factory function creates unique instance:** ```vue ``` ## API Explanation The `inject()` function has multiple signatures: ```ts // 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) ```vue ``` ### Object Defaults (Factory Required) ```vue ``` ### Array Defaults (Factory Required) ```vue ``` ### Class Instance Defaults (Factory Required) ```vue ``` ## Options API Syntax In Options API, use the object syntax with a `default` factory function: ```js 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 ```vue ``` ## TypeScript: Typing Factory Defaults ```ts import { inject } from 'vue' import type { InjectionKey } from 'vue' interface Config { apiUrl: string debug: boolean features: string[] } const ConfigKey: InjectionKey = 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: ```ts // 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:** ```ts it('test with provider', () => { const wrapper = mount(MyComponent, { global: { provide: { config: { debug: false, apiUrl: '' } } } }) }) ``` ## Reference - [Vue.js inject() API Reference](https://vuejs.org/api/composition-api-dependency-injection.html#inject) - [Vue.js Provide/Inject Guide](https://vuejs.org/guide/components/provide-inject.html)