When Vue 3 Provide/Inject Fails: Key Insights and Fixes
This article explains how Vue 3's provide/inject mechanism works, demonstrates proper usage with strings, refs, and Symbols, analyzes why injected data may be unavailable across applications, and offers practical solutions such as using Symbol.for and application‑level provides.
In a host project that uses a component library, a key named date with the current timestamp is injected via provide. The article first asks whether a business component can access this injected data.
Dependency Injection
provide
To supply data to descendant components, use the provide() function.
<script setup>
import { provide } from 'vue'
provide('message', 'hello!')
</script>If not using <script setup>, ensure provide() is called synchronously inside setup():
import { provide } from 'vue'
export default {
setup() {
provide('message', 'hello!')
}
} provide()takes two arguments: the injection key (string, number, or Symbol) and the value, which can be any type, including a reactive ref:
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)Providing a reactive value lets descendants maintain a reactive connection with the provider.
Application‑level provide
Data can also be provided at the app level, making it available to all components:
import { createApp } from 'vue'
const app = createApp({})
app.provide('message', 'hello!')inject
Descendant components retrieve provided data using inject():
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>If the injected value is a ref, the reference itself is returned without automatic unwrapping, preserving reactivity.
When not using <script setup>, call inject() synchronously inside setup():
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}Default values
If no ancestor provides the key, inject() throws a warning. A default value can be supplied, similar to props:
// If "message" is not provided, value will be "default"
const value = inject('message', 'default')For expensive defaults, use a factory function to avoid unnecessary computation:
const value = inject('key', () => new ExpensiveClass())Using Symbol as the injection key
In large applications, using a Symbol as the key prevents naming collisions. Export the Symbol from a dedicated file: export const myInjectionKey = Symbol() Provide and inject using the Symbol:
// provider
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, { /* data */ })
// injector
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)Implementation Details
The source code of provide creates a new provides object that inherits from the parent’s provides when the keys differ, ensuring the nearest ancestor’s value is used.
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
if (!currentInstance) {
if (__DEV__) {
warn('provide() can only be used inside setup().')
}
} else {
let provides = currentInstance.provides
const parentProvides = currentInstance.parent && currentInstance.parent.provides
if (parentProvides === provides) {
provides = currentInstance.provides = Object.create(parentProvides)
}
provides[key as string] = value
}
} export function inject<T>(key: InjectionKey<T> | string, defaultValue?: unknown, treatDefaultAsFactory = false) {
const instance = currentInstance || currentRenderingInstance
if (instance) {
const provides = instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
if (provides && (key as string | symbol) in provides) {
return provides[key as string]
} else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance.proxy)
: defaultValue
} else if (__DEV__) {
warn(`injection "${String(key)}" not found.`)
}
} else if (__DEV__) {
warn('inject() can only be used inside setup() or functional components.')
}
}Source: packages/runtime-core/src/apiInject.ts
Why Inject May Fail
When the host project and the business component library use different Symbol instances for the same logical key, the injection fails because each Symbol is unique per application.
To share a Symbol across applications, use Symbol.for() to register or retrieve a global Symbol:
export const date = Symbol.for('date');Summary
Ensure the provider and injector are in the same component tree.
If using a Symbol as the key, both components must belong to the same app.
For cross‑application injection, create a global Symbol with Symbol.for.
References
[1] Vue 3.0 inject source code: https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiInject.ts
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BaiPing Technology
Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
