Vue

Lesson 09

Provide and inject boundaries

Use provide and inject for narrow context values with typed keys, not as a hidden app-wide dependency channel.

Good Code

reviewThemeContext.ts
import type { InjectionKey, Ref } from "vue";
import { inject, provide, readonly, ref } from "vue";

type ReviewTheme = "light" | "dark";
const reviewThemeKey: InjectionKey<Readonly<Ref<ReviewTheme>>> =
  Symbol("review-theme");

export function provideReviewTheme() {
  // Consumers can read the theme but cannot rewrite it directly.
  const theme = ref<ReviewTheme>("light");
  provide(reviewThemeKey, readonly(theme));
  return { theme };
}

export function useReviewTheme() {
  const theme = inject(reviewThemeKey);
  if (!theme) throw new Error("Review theme provider is missing");
  return theme;
}

Bad Code

appContext.ts
import { inject, provide, reactive } from "vue";

const appContextKey = Symbol("app");

// One mutable object becomes a hidden dependency for many components.
export function provideAppContext() {
  const app = reactive({ user: null, reviews: [], settings: {}, theme: "light" });
  provide(appContextKey, app);
}

export function useAppContext() {
  return inject(appContextKey) as any;
}

Review Notes

What to review

Good Code

The good version provides a narrow, typed context value and keeps mutation at the provider boundary.

Bad Code

The bad version injects a broad mutable object. Components can become coupled to unrelated user, review, settings, and theme state.

Takeaways

  • Injected values should have a small contract, a typed key, and a provider close to the feature that owns the context.