Hack Frontend Community

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)