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.

BaiPing Technology
BaiPing Technology
BaiPing Technology
When Vue 3 Provide/Inject Fails: Key Insights and Fixes

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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendVue.jsdependency-injectionSymbolInjectprovide
BaiPing Technology
Written by

BaiPing Technology

Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.