patternjavascriptvueTip
Vue 3 provide/inject for deep component communication
Viewed 0 times
Vue 3.0+
provideinjectInjectionKeydependency injectionprop drillingcomponent tree
Error Messages
Problem
Prop drilling through many component layers to pass data to deeply nested children. Using Pinia for data that is scoped to one component tree is overkill.
Solution
Use provide/inject with a typed injection key:
// types.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Ref<string>;
setTheme: (t: string) => void;
}
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
// Parent component
<script setup lang="ts">
import { provide, ref } from 'vue';
import { ThemeKey } from './types';
const theme = ref('light');
function setTheme(t: string) { theme.value = t; }
provide(ThemeKey, { theme, setTheme });
</script>
// Deep child component
<script setup lang="ts">
import { inject } from 'vue';
import { ThemeKey } from './types';
const themeCtx = inject(ThemeKey);
// themeCtx is undefined if no provider above it
if (!themeCtx) throw new Error('ThemeKey not provided');
const { theme, setTheme } = themeCtx;
</script>
// types.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Ref<string>;
setTheme: (t: string) => void;
}
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
// Parent component
<script setup lang="ts">
import { provide, ref } from 'vue';
import { ThemeKey } from './types';
const theme = ref('light');
function setTheme(t: string) { theme.value = t; }
provide(ThemeKey, { theme, setTheme });
</script>
// Deep child component
<script setup lang="ts">
import { inject } from 'vue';
import { ThemeKey } from './types';
const themeCtx = inject(ThemeKey);
// themeCtx is undefined if no provider above it
if (!themeCtx) throw new Error('ThemeKey not provided');
const { theme, setTheme } = themeCtx;
</script>
Why
provide/inject is Vue's dependency injection system for component trees. It avoids prop drilling without the overhead of a global store. Using InjectionKey (Symbol) with a TypeScript generic gives compile-time type safety and prevents key collisions.
Gotchas
- inject() returns undefined if called outside a provider — always handle the undefined case
- Providing a ref() keeps reactivity end-to-end — don't provide .value directly
- provide/inject is not reactive to the key itself — only the provided value can be reactive
- App-level provide (app.provide()) is available everywhere, replacing some global state needs
Code Snippets
Typed provide/inject with fallback
import type { InjectionKey, Ref } from 'vue';
export const CountKey: InjectionKey<Ref<number>> = Symbol('count');
// Provider
provide(CountKey, ref(0));
// Consumer
const count = inject(CountKey, ref(0)); // with fallbackContext
When passing data to deeply nested Vue 3 components without prop drilling
Revisions (0)
No revisions yet.