Provide, Inject, and Dynamic Components

Introduction

Props work well for parent → child, but passing data through many middle layers is prop drilling. provide / inject share values across the tree. Dynamic components swap which component renders with <component :is>. defineAsyncComponent loads components lazily. This chapter covers when each pattern fits.

Prerequisites

Prop Drilling Problem

text
App → Layout → Sidebar → Menu → MenuItem (needs theme)

Passing theme through every layer clutters APIs. Provide/inject or Pinia solve this—Pinia for global app state; provide/inject for localized subtree context.

provide and inject

Parent provides:

Deep child injects:

Code explanation:

  • provide(key, value) in ancestor
  • inject(key) in descendant—any depth
  • Use readonly when children should not mutate directly

Injection Key with Symbol

Avoid string collisions:

typescript
import type { InjectionKey, Ref } from "vue";
 
export const themeKey: InjectionKey<Ref<"light" | "dark">> = Symbol("theme");
 
provide(themeKey, theme);
const theme = inject(themeKey);

Default Value

typescript
const theme = inject("theme", ref("light"));

Form Context Example

Wizard form sharing validation state:

Step components inject('wizard') and read/write formState.

For app-wide forms, Pinia may scale better.

Dynamic Components

Switch view by name:

Use shallowRef for component objects—avoid deep reactivity on component definitions.

String is with registered name (less common in SFC imports):

vue
<component :is="'HomeView'" />

Vue Router uses dynamic components internally—Vue Router.

defineAsyncComponent

Lazy load heavy panels:

Code explanation:

  • Split code into separate chunk—loads on first render
  • Suspense shows fallback while async component loads
  • Route-level lazy loading preferred for pages—Router chapter

Loading and error options:

typescript
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Heavy.vue"),
  loadingComponent: LoadingSpinner,
  errorComponent: LoadError,
  delay: 200,
  timeout: 10000,
});

Dynamic + Transition

vue
<Transition name="fade" mode="out-in">
  <component :is="activeTab" :key="activeName" />
</Transition>

:key forces transition when switching components.

When to Use What

PatternUse
Props / emitDirect parent-child
SlotsCustom markup in child
Provide / injectTheme, i18n, form wizard context
PiniaGlobal user, cart, settings
Dynamic componentTabs, plugin panels
Async componentLarge optional UI

FAQ

provide/inject vs Pinia?

Provide for subtree scope; Pinia for app-wide shared state with devtools.

inject undefined?

Provider missing or wrong key—use defaults or throw in dev.

reactive provide?

Provide ref or reactive; inject readonly when children should emit events instead of mutating.

component
string vs object?

Import object is tree-shaking friendly; string needs global registration.

Async component errors?

Use errorComponent or Router onError.

SSR with async component?

Requires Suspense / Nuxt—client-only wrap ClientOnly in Nuxt.

Same as React Context?

Similar to Context + dynamic React.lazy—Vue built-ins differ in API.