Provide / Inject in Vue.js

Prop Drilling

Prop drilling occurs when deeply nested components need data from a distant ancestor. With traditional props, the data has to be passed through all intermediate components, leading to unnecessary prop declarations. This is known as "props drilling."

Provide

The provide function in Vue.js allows a parent component to serve as a dependency provider for all its descendants. This eliminates the need for prop drilling. Here's an example:

<script setup>
import { provide } from 'vue'

provide('message', 'hello!')
</script>

You can also provide reactive values, allowing descendants to establish a reactive connection:

<script setup>
import { ref, provide } from 'vue'

const count = ref(0)
provide('key', count)
</script>

Additionally, at the app level, you can provide data available to all components:

import { createApp } from 'vue'

const app = createApp({})

app.provide('message', 'hello!')

Inject

To access provided data in a descendant component, you use the inject function:

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

If the provided value is a ref, it retains reactivity when injected.

Default Values

You can set default values for injected properties to handle cases where the key is not provided:

const value = inject('message', 'default value')

For more complex default values, you can use a factory function:

const value = inject('key', () => new ExpensiveClass(), true)

Working with Reactivity

It's recommended to keep mutations to reactive state inside the provider component. If updates are needed from an injector component, provide a function responsible for state mutation:

<!-- Inside provider component -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>

You can use readonly() to ensure data passed through provide cannot be mutated by the injector component.

<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

Working with Symbol Keys

For larger applications or components used by other developers, it's recommended to use Symbol injection keys to avoid potential collisions:

// keys.js
export const myInjectionKey = Symbol()

In the provider and injector components:

// In provider component
<script setup>
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, {
  /* data to provide */
})
</script>
// In injector component
<script setup>
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)
</script>

This approach enhances code organization and reduces the likelihood of naming conflicts.

Last updated