Provide / Inject
This page assumes you've already read the Components Basics. Read that first if you are new to components.
Prop Drilling
Usually, when we need to pass data from the parent to a child component, we use props. However, imagine the case where we have a large component tree, and a deeply nested component needs something from a distant ancestor component. With only props, we would have to pass the same prop across the entire parent chain:
Notice although the <Footer>
component may not care about these props at all, it still needs to declare and pass them along just so <DeepChild>
can access them. If there is a longer parent chain, more components would be affected along the way. This is called "props drilling" and definitely isn't fun to deal with.
We can solve props drilling with provide
and inject
. A parent component can serve as a dependency provider for all its descendants. Any component in the descendant tree, regardless of how deep it is, can inject dependencies provided by components up in its parent chain.
Provide
To provide data to a component's descendants, use the provide()
function:
<script setup>
import { provide } from 'vue'
provide(/* key */ 'message', /* value */ 'hello!')
</script>
If not using <script setup>
, make sure provide()
is called synchronously inside setup()
:
import { provide } from 'vue'
export default {
setup() {
provide(/* key */ 'message', /* value */ 'hello!')
}
}
The provide()
function accepts two arguments. The first argument is called the injection key, which can be a string or a Symbol
. The injection key is used by descendent components to lookup the desired value to inject. A single component can call provide()
multiple times with different injection keys to provide different values.
The second argument is the provided value. The value can be of any type, including reactive state such as refs:
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)
Providing reactive values allows the descendent components using the provided value to establish a reactive connection to the provider component.
To provide data to a component's descendants, use the provide
option:
export default {
provide: {
message: 'hello!'
}
}
For each property in the provide
object, the key is used by child components to locate the correct value to inject, while the value is what ends up being injected.
If we need to provide per-instance state, for example data declared via the data()
, then provide
must use a function value:
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
// use function syntax so that we can access `this`
return {
message: this.message
}
}
}
However, do note this does not make the injection reactive. We will discuss making injections reactive below.
App-level Provide
In addition to providing data in a component, we can also provide at the app level:
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* key */ 'message', /* value */ 'hello!')
App-level provides are available to all components rendered in the app. This is especially useful when writing plugins, as plugins typically wouldn't be able to provide values using components.
Inject
To inject data provided by an ancestor component, use the inject()
function:
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
If the provided value is a ref, it will be injected as-is and will not be automatically unwrapped. This allows the injector component to retain the reactivity connection to the provider component.
Full provide + inject Example with Reactivity
Again, if not using <script setup>
, inject()
should only be called synchronously inside setup()
:
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}
To inject data provided by an ancestor component, use the inject
option:
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
}
}
Injections are resolved before the component's own state, so you can access injected properties in data()
:
export default {
inject: ['message'],
data() {
return {
// initial data based on injected value
fullMessage: this.message
}
}
}
Injection Aliasing
When using the array syntax for inject
, the injected properties are exposed on the component instance using the same key. In the example above, the property was provided under the key "message"
, and injected as this.message
. The local key is the same as the injection key.
If we want to inject the property using a different local key, we need to use the object syntax for the inject
option:
export default {
inject: {
/* local key */ localMessage: {
from: /* injection key */ 'message'
}
}
}
Here, the component will locate a property provided with the key "message"
, and then expose it as this.localMessage
.
Injection Default Values
By default, inject
assumes that the injected key is provided somewhere in the parent chain. In the case where the key is not provided, there will be a runtime warning.
If we want to make an injected property work with optional providers, we need to declare a default value, similar to props:
// `value` will be "default value"
// if no data matching "message" was provided
const value = inject('message', 'default value')
In some cases, the default value may need to be created by calling a function or instantiating a new class. To avoid unnecessary computation or side effects in case the optional value is not used, we can use a factory function for creating the default value:
const value = inject('key', () => new ExpensiveClass())
export default {
// object syntax is required
// when declaring default values for injections
inject: {
message: {
from: 'message', // this is optional if using the same key for injection
default: 'default value'
},
user: {
// use a factory function for non-primitive values that are expensive
// to create, or ones that should be unique per component instance.
default: () => ({ name: 'John' })
}
}
}
Working with Reactivity
When using reactive provide / inject values, it is recommended to keep any mutations to reactive state inside of the provider whenever possible. This ensures that the provided state and its possible mutations are co-located in the same component, making it easier to maintain in the future.
There may be times when we need to update the data from an injector component. In such cases, we recommend providing a function that is responsible for mutating the state:
<!-- 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>
<!-- in injector component -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
Finally, you can wrap the provided value with readonly()
if you want to ensure that the data passed through provide
cannot be mutated by the injected component.
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
In order to make injections reactively linked to the provider, we need to provide a computed property using the computed() function:
import { computed } from 'vue'
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
return {
// explicitly provide a computed property
message: computed(() => this.message)
}
}
}
Full provide + inject Example with Reactivity
The computed()
function is typically used in Composition API components, but can also be used to complement certain use cases in Options API. You can learn more about its usage by reading the Reactivity Fundamentals and Computed Properties with the API Preference set to Composition API.
Temporary Config Required
The above usage requires setting app.config.unwrapInjectedRef = true
to make injections automatically unwrap computed refs. This will become the default behavior in Vue 3.3 and this config is introduced temporarily to avoid breakage. It will no longer be required after 3.3.
Working with Symbol Keys
So far, we have been using string injection keys in the examples. If you are working in a large application with many dependency providers, or you are authoring components that are going to be used by other developers, it is best to use Symbol injection keys to avoid potential collisions.
It's recommended to export the Symbols in a dedicated file:
// keys.js
export const myInjectionKey = Symbol()
// in provider component
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* data to provide */
})
// in injector component
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
See also: Typing Provide / Inject
// in provider component
import { myInjectionKey } from './keys.js'
export default {
provide() {
return {
[myInjectionKey]: {
/* data to provide */
}
}
}
}
// in injector component
import { myInjectionKey } from './keys.js'
export default {
inject: {
injected: { from: myInjectionKey }
}
}