Vue.js Provide and Inject

Provide and inject let a parent component share data with any descendant, no matter how deep the family tree runs. Props work well for one level, but a value that must travel through five layers forces every middle component to accept and forward it. Developers call this problem prop drilling. Provide and inject skip the middle layers and deliver the value straight to the component that needs it.

Think of a radio station. The station broadcasts one signal, and any radio within range tunes in. The towns between the station and the listener do not need to pass the signal along by hand. The parent provides the broadcast, and a descendant injects it.

The Prop Drilling Problem

Diagram: Prop Drilling

App (has theme = "dark")
 |  :theme
 v
Layout       (only passes theme along)
 |  :theme
 v
Sidebar      (only passes theme along)
 |  :theme
 v
MenuButton   (finally uses theme)

Layout and Sidebar never use the theme. They carry it because the button needs it.

Provide a Value in the Parent

The ancestor calls provide(key, value). The key is a name, and the value is any data or function.

<!-- App.vue -->
<script setup>
import { provide, ref } from "vue";

const theme = ref("dark");
provide("theme", theme);
</script>

Inject the Value in a Descendant

Any component below the provider calls inject(key) and receives the value.

<!-- MenuButton.vue -->
<script setup>
import { inject } from "vue";

const theme = inject("theme");
</script>

<template>
  <button>Current theme: {{ theme }}</button>
</template>

Diagram: Direct Delivery

App  --provide("theme")--+
 |                       |
Layout                   |
 |                       |
Sidebar                  |
 |                       |
MenuButton  <--inject("theme")--+

Because the provided value is a ref, the button updates whenever the app changes the theme.

Provide Functions for Safe Changes

A descendant that edits shared data directly makes bugs hard to trace. Provide a function next to the value so the owner stays in charge of every change.

// Parent
const theme = ref("dark");
function toggleTheme() {
  theme.value = theme.value === "dark" ? "light" : "dark";
}
provide("theme", { theme, toggleTheme });

// Child
const { theme, toggleTheme } = inject("theme");

The parent defines how the data changes, and the child only asks for a change. Every edit passes through one place.

Protect Data with readonly

Wrap a provided ref in readonly() to block direct edits from children.

import { provide, ref, readonly } from "vue";

const count = ref(0);
provide("count", readonly(count));

Default Values

An injection fails with a warning when no ancestor provides the key. A second argument supplies a fallback.

const language = inject("language", "English");

The component receives "English" when nobody provides a language.

Safer Keys with Symbols

String keys can clash in large projects, because two teams might pick the name "user" for different data. A Symbol is a unique key that nothing else can match. Store keys in a shared file.

// keys.js
export const userKey = Symbol("user");

// Parent
import { userKey } from "./keys.js";
provide(userKey, currentUser);

// Child
import { userKey } from "./keys.js";
const user = inject(userKey);

Provide Data to the Whole App

The application object also offers provide. Values set there reach every component in the project.

const app = createApp(App);
app.provide("appName", "My Shop");
app.mount("#app");

Diagram: Provider Levels

app.provide()        --> every component in the app
component provide()  --> that component and all its descendants
Nearest provider wins when two share the same key

Choosing Between Props, Provide, and a Store

  • Props suit direct parent-to-child data and keep the flow easy to follow.
  • Provide and inject suit shared settings such as theme, language, or a form context across a component family.
  • A global store suits data that unrelated parts of the application need, such as a login session.

Summary

Provide and inject deliver data from an ancestor straight to any descendant and remove the need for prop drilling. Provide refs for live updates, add functions for controlled changes, wrap values in readonly() to block edits, and set default values for safety. Symbol keys prevent naming clashes, and app-level provide shares values with every component.