Chapter 36: Vue Provide/Inject
Provide / Inject
This is Vue’s built-in dependency injection system — a way to pass data or functions down through many levels of components without having to pass them manually through every single prop layer (a problem called prop drilling).
Think of it like this:
Imagine you’re in a big family house (your app). The grandfather (top-level component) has the Wi-Fi password. Instead of telling every child, grandchild, great-grandchild one by one… grandfather just announces loudly → “Anyone who needs the Wi-Fi password can come ask me directly!” Then any grandchild in any room can ask (inject) and get the password — without asking mom, dad, uncle, aunt along the way.
provide = grandfather announces / puts something on the table inject = any descendant can reach out and take it
When Should You Use Provide/Inject? (Real 2026 Rules)
Use it only when:
- Data needs to be available to deeply nested components (3+ levels down)
- Passing via props would create long ugly prop chains
- The data is global to a subtree (theme, user info, language, modal manager, toast service, etc.)
- You want clean, decoupled architecture
Do NOT use it when:
- Only 1–2 levels deep → just use props (clearer)
- You need reactivity from top to bottom → prefer Pinia or provide reactive object
- It’s truly global to the whole app → use Pinia store or App-level provide
Modern Vue 3 Syntax (2026 – <script setup>)
Basic Example – Theme Provider
Ancestor Component (provides theme) (e.g. App.vue or Layout.vue)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<!-- src/App.vue or src/layouts/DefaultLayout.vue --> <template> <div :class="`theme-{currentTheme}`"> <router-view /> </div> </template> <script setup lang="ts"> import { ref, provide } from 'vue' // 1. Create reactive theme const currentTheme = ref<'light' | 'dark'>('light') // 2. Provide it to all descendants provide('theme', currentTheme) // key can be string or Symbol // Optional: also provide toggle function provide('toggleTheme', () => { currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light' }) </script> <style> .theme-light { background: #f8fafc; color: #111827; } .theme-dark { background: #111827; color: #f8fafc; } </style> |
Deeply Nested Child Component (injects theme) (e.g. 5 levels down – UserProfile → Settings → ThemeToggleButton)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<!-- src/components/settings/ThemeToggleButton.vue --> <template> <button @click="toggleTheme" class="toggle-btn"> Switch to {{ currentTheme === 'light' ? 'Dark' : 'Light' }} Mode </button> </template> <script setup lang="ts"> import { inject } from 'vue' // 1. Inject the theme ref (reactive!) const currentTheme = inject('theme') as Ref<'light' | 'dark'> // 2. Inject the toggle function const toggleTheme = inject('toggleTheme') as () => void </script> <style scoped> .toggle-btn { padding: 0.8rem 1.5rem; border-radius: 9999px; background: var(--bg); color: var(--text); border: 1px solid var(--border); cursor: pointer; } /* Use CSS variables or classes from theme */ :host-context(.theme-dark) { --bg: #374151; --text: #f3f4f6; --border: #4b5563; } :host-context(.theme-light) { --bg: white; --text: #111827; --border: #d1d5db; } </style> |
Important Rules & Best Practices (2026)
| Feature / Question | Answer / Best Practice |
|---|---|
| Provide value types | Can be any JS value: ref, reactive, plain object, function, class instance, etc. |
| Is it reactive? | Yes — if you provide a ref or reactive object → changes propagate |
| Can child change provided value? | Yes — if it’s a ref/reactive → child can .value++ (but prefer immutable + functions) |
| Key types | String or Symbol (better for avoiding collisions) |
| Inject default value | inject(‘theme’, ‘light’) or inject(‘theme’, () => ‘light’) |
| Required inject | inject(‘theme’, undefined, true) → throws if not found |
| Multiple levels of provide | Child can override parent’s provide → closest ancestor wins |
| Provide inside setup() vs onBeforeMount? | Inside <script setup> or setup() — before children mount |
| SSR / Nuxt? | Works fine — but be careful with client-only APIs |
Better Pattern – Provide Reactive Object + Functions (Clean & Safe)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Top-level provider const themeState = reactive({ mode: 'light', primaryColor: '#3b82f6' }) function toggleMode() { themeState.mode = themeState.mode === 'light' ? 'dark' : 'light' } provide('themeState', themeState) provide('toggleMode', toggleMode) |
|
0 1 2 3 4 5 6 7 8 |
// Deep child const themeState = inject('themeState') as { mode: string; primaryColor: string } const toggleMode = inject('toggleMode') as () => void |
→ Child can read reactively, but cannot mutate directly → cleaner architecture
Quick Summary Table – Provide/Inject vs Alternatives
| Situation | Best Choice | Why |
|---|---|---|
| 1–2 levels deep | Props | Clear, explicit, easy to trace |
| 3–5 levels deep, simple value | Provide/Inject | Avoids prop drilling |
| Deep + global-like (theme, i18n, auth) | Provide/Inject or Pinia | Pinia better for very large apps |
| Needs mutations & reactivity | Provide reactive object + functions | Safe & predictable |
| Complex state + caching + async | Pinia / Zustand / Vuex | Full store with devtools |
Your Mini Practice Task
- Create ThemeProvider.vue (wraps <router-view>) that provides theme state + toggle function
- Create ThemeToggleButton.vue 4 levels deep that injects & uses it
- Add a computed class on root element that reacts to theme change
Any part confusing? Want to see:
- Provide/Inject with TypeScript InjectionKey?
- Provide inside setup() vs Composition API functions?
- Real example with modal manager / toast service?
- Provide vs Pinia vs Context-like pattern comparison?
Just tell me — we’ll build the next clean, scalable example together 🚀
Happy providing & injecting from Hyderabad! 💙
