provide/inject in Vue.js
What is provide/inject?
provide and inject are a pair of APIs for passing data from a parent component to any descendant without explicitly passing through props at each level.
This solves the "prop drilling" problem - when you need to pass props through many components.
Basic Usage
Composition API
<!-- App.vue (parent) -->
<template>
<ChildComponent />
</template>
<script setup>
import { provide, ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const theme = ref('dark')
provide('theme', theme)
</script>
<!-- GrandchildComponent.vue (descendant) -->
<template>
<div :class="theme">
Theme: {{ theme }}
</div>
</template>
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
</script>
Reactivity
Passing Reactive Data
<!-- App.vue -->
<template>
<button @click="theme = theme === 'dark' ? 'light' : 'dark'">
Toggle Theme
</button>
<ChildComponent />
</template>
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
// Pass ref, not value
provide('theme', theme)
</script>
<!-- DeepChild.vue -->
<template>
<div :class="theme">
Current theme: {{ theme }}
</div>
</template>
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
// theme will automatically update on change
</script>
Passing Functions for Modification
<!-- App.vue -->
<script setup>
import { provide, ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++
}
provide('count', count)
provide('increment', increment)
</script>
<!-- DeepChild.vue -->
<template>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</template>
<script setup>
import { inject } from 'vue'
const count = inject('count')
const increment = inject('increment')
</script>
Default Values
<script setup>
import { inject } from 'vue'
// If 'theme' not provided, use 'light'
const theme = inject('theme', 'light')
// Default value as function
const user = inject('user', () => ({ name: 'Guest' }))
// Third parameter - value is computed as factory
const config = inject('config', () => {
return { /* complex logic */ }
}, true)
</script>
Symbol Keys
Using Symbol prevents name conflicts:
// keys.js
export const ThemeKey = Symbol('theme')
export const UserKey = Symbol('user')
<!-- App.vue -->
<script setup>
import { provide, ref } from 'vue'
import { ThemeKey, UserKey } from './keys'
const theme = ref('dark')
const user = ref({ name: 'John' })
provide(ThemeKey, theme)
provide(UserKey, user)
</script>
<!-- Child.vue -->
<script setup>
import { inject } from 'vue'
import { ThemeKey, UserKey } from './keys'
const theme = inject(ThemeKey)
const user = inject(UserKey)
</script>
Application-level provide
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.provide('apiUrl', 'https://api.example.com')
app.provide('appName', 'My App')
app.mount('#app')
<!-- AnyComponent.vue -->
<script setup>
import { inject } from 'vue'
const apiUrl = inject('apiUrl')
const appName = inject('appName')
console.log(apiUrl) // 'https://api.example.com'
console.log(appName) // 'My App'
</script>
provide/inject vs props
When to use props
<!-- Direct parent-child relationship -->
<ChildComponent :title="title" :count="count" />
Use props when:
- Components are directly related
- Need explicit data connection
- Simple hierarchy (1-2 levels)
When to use provide/inject
<!-- Deep hierarchy -->
<GrandParent>
<Parent>
<Child>
<DeepChild /> <!-- Needs data from GrandParent -->
</Child>
</Parent>
</GrandParent>
Use provide/inject when:
- Deep component hierarchy
- Many intermediate components
- Global context (theme, locale, API)
Common Mistakes
Passing non-reactive value
<!-- Wrong -->
<script setup>
import { provide, ref } from 'vue'
const count = ref(0)
provide('count', count.value) // Passes only value, not ref!
</script>
<!-- Correct -->
<script setup>
import { provide, ref } from 'vue'
const count = ref(0)
provide('count', count) // Passes ref
</script>
Mutating data in child component
<!-- Wrong -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
// Not recommended to mutate directly
theme.value = 'dark'
</script>
<!-- Correct - pass function for modification -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const setTheme = inject('setTheme')
setTheme('dark')
</script>
Conclusion
provide/inject:
- Pass data through hierarchy without props
- Solves prop drilling problem
- Supports reactivity
- Can use Symbol for keys
- Suitable for global state (theme, locale)
- Doesn't replace Vuex/Pinia for complex state management
In interviews:
Important to be able to:
- Explain what provide/inject is and why it's needed
- Describe the prop drilling problem
- Show how reactivity works with provide/inject
- Explain difference between provide/inject and props
- Give usage examples (theme, API client)