What this usually means
When a Vue child component's inject property fails to receive a value, it's often due to a disconnect in the actual component tree—either the provider isn't an ancestor, or the provide is invoked at a different instance than you expect. Other subtle causes include setup() being called before provide(), different Vue app roots (multiple Vue.createApp), or provide being defined as a function instead of an object in Options API. Reactivity loss is also common if a plain value is provided instead of a ref or reactive object.
The first ten minutes — establish facts before touching code.
- 1In browser devtools, inspect the component tree and confirm the provider is a direct ancestor of the injector.
- 2Log the output of provide() in the provider and inject() in the child—add console.log inside setup() or created hooks to verify execution order.
- 3Check for multiple Vue app roots (use window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps in console).
- 4In setup() usage, ensure that provide() is called before child components mount.
- 5Look out for hot module replacement (HMR) issues—do a hard refresh and retest.
- 6Review component registration: confirm components are not dynamically loaded or registered under a different app context.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchProvider and child component .vue files, specifically the provide and inject sections
- searchmain.js/main.ts for multiple Vue.createApp calls
- searchVue component tree in devtools, including app instance roots
- searchsetup() and lifecycle hooks for execution order debugging
- searchBrowser console for injection-related warnings
- searchWebpack or Vite HMR logs if behavior changes after hot reload
Practical causes, not theory. These are the things you will actually find.
- warningProvider and injector are in separate Vue app instances (multiple Vue.createApp)
- warningProvider is a sibling or not a true ancestor in the render tree
- warningProvide is set up in a hook that fires after injection setup (e.g., mounted instead of setup/created)
- warningMissing return from provide() function in Composition API
- warningProvide returns a plain value, not a reactive/ref (no reactivity)
- warningIncorrect inject key (string typo or Symbol mismatch)
- warningComponent is asynchronously loaded and mounted outside provider context
Concrete fix directions. Pick the one that matches your root cause.
- buildRestructure component tree so provider is a direct ancestor of every injector
- buildCall provide() at the top of the setup() in provider components, not later
- buildWrap provided values in ref() or reactive() to maintain reactivity
- buildUse the exact same key (string or Symbol) in both provide and inject
- buildConsolidate to a single Vue app root if possible
- buildFor SSR or async loading, guarantee provider mounts before injector
A fix you cannot prove is a guess. Close the loop.
- verifiedTrigger a change in the provider's value and observe the injected value update in child
- verifiedConsole.log the injected value on every render of the child; confirm it's no longer undefined
- verifiedRemove default value from inject and confirm injection now supplies the intended value
- verifiedUse Vue devtools to inspect both provided and injected values live
- verifiedRestart dev server and verify fix persists after full reload
Things that make this bug worse or harder to find.
- warningCalling provide() after child has mounted—it’s too late for injection to work
- warningUsing a different type for inject key (Symbol in provider, string in injector)
- warningProviding a primitive value and expecting child to track reactivity
- warningAssuming parent-child relationship in component files matches the render tree
- warningFixating on props when the issue is unrelated to prop passing
Inject Value Always Undefined In Nested Vue Components
Timeline
- 10:15Merged a new layout component wrapping the page provider.
- 10:25QA reports that the child search box always has 'undefined' in place of expected token.
- 10:27Console logs in setup() show provided value exists, but child inject receives undefined.
- 10:33Checked Vue devtools—two app roots detected due to misapplied Vue.createApp.
- 10:39Found that provider was mounted under one app, child under another.
- 10:41Refactored to ensure both provider and child mount under single app instance.
- 10:44Child now receives injected token as expected—bug resolved.
I had set up authentication with a top-level provider and expected to inject the auth token into all route children. Suddenly, a new layout component broke the search box: inject always gave 'undefined'.
Initial debugging didn't reveal syntax errors. The provide was definitely running, and the token existed at the provider. It wasn't until I inspected the Vue devtools that I saw two roots—something I hadn't noticed before.
The new layout was registering itself under a different Vue app. Once I unified everything under a single createApp call, injection immediately began working again. The whole issue was component context, not simple typos or timing.
Root cause
Provider and injector were under separate Vue app instances after a refactor introduced a second createApp call.
The fix
Collapse to a single Vue app root so component hierarchy is unified; now provide/inject connections are correct.
The lesson
Always verify your provider and consumer are in the same app context—multiple roots are a silent killer for provide/inject.
Vue's provide/inject only works within the same app context. This means that even if components appear to be in the same tree visually or by file structure, if they're under different Vue.createApp calls, injection won't work. This is especially easy to miss on larger projects where micro-frontends or lazy-loading introduce unintentional multiple roots.
Use window.__VUE_DEVTOOLS_GLOBAL_HOOK__.apps in your browser's console to display all live app instances. If you see more than one, this almost always causes provide/inject failures.
In the Composition API, provide() must run before any child component's setup() tries to inject. If you delay your provide() call (e.g., place it in a mounted or after a fetch), the child will have already called inject and received the default value or undefined.
Always call provide() at the very top of the provider's setup(). If you need asynchronous data, provide a ref or reactive object immediately and mutate it later, so injection remains consistent.
Providing a primitive value (like a string or number) means injection gives a static copy—not reactive. To keep injected values reactive, always provide a ref() or reactive() object, and inject the same type.
For example: provide('foo', someRef) and inject('foo').value keep the child in sync as the ref updates, but provide('foo', 'bar') leaves the child stuck with 'bar' even if the provider changes.
Async-loaded components or SSR hydration can cause inject to run before the provider is ready. If you have SSR, verify initialization order, and for async-loaded children, ensure the provider is fully mounted before any children render.
Use explicit mounting and loading guards to guarantee the provider is present. Console.log the injection in every child render to catch timing issues.
Frequently asked questions
Can I use provide/inject across different Vue app instances?
No. Provide/inject only works within a single app instance. If you use multiple Vue.createApp roots, injection will always fail between them.
Why is my injected value static and not reactive?
If you provide a primitive, injection will be static. Provide a ref() or reactive() to ensure the child receives updates as the value changes.
Can I provide/inject in Options API components?
Yes, but use object syntax for provide in Options API, not a function. Using a function will not work as expected outside Composition API.
Why does my inject default value always get used?
Either the provider is missing at the expected level, or provide() is called too late (e.g., after the child’s setup), so injection falls back to default.
Does provide/inject work with async-loaded components?
Yes, but only if the provider is mounted and available before the child renders. Improper mounting order will break injection.