Provide / Inject from Vue.js
When we want share data with child components, usually we uses the props. But imagine the case where we have a large component three. and a deeply nested component needs something from a distant ancestor component.
Tags
Prop Drilling (The problem)
When we want share data with child components, usually we uses the props. But imagine the case where we have a large component three. and a deeply nested component needs something from a distant ancestor component. With only props, we would have to pass the same props 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
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, we can use the following code:
- Options API
<script>
export default {
provide: {
exampleData: 'Hello World'
}
}
</script>
- Composition API
<script setup>
provide('exampleData', 'Hello World')
</script>
App level:
It provides the data to all components rendered in the app.
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* key */ 'exampleData', /* value */ 'Hello World')
Inject
To inject the data provided by an ancestor component, use the inject
:
- Option API
<script>
export default {
inject: ['exampleData'] // It will be available in "this" of the component to be used.
data() {
return {
// initial data based on injected value.
data: this.exampleData
}
}
}
</script>
- Composition API
<script setup>
const exampleData = inject('exampleData')
</script>
How to define a default value
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:
- Options API
<script>
export default {
// object syntax is required
// when declaring default values for injections
inject: {
exampleData: {
default: 'default value'
},
userData: {
// use a factory function for non-primitive values that are expensive
// to create, or ones that should be unique per component instance.
default: () => ({ firstName: 'Everton', lastName: 'Vanoni' })
}
}
}
</script>
- Composition API
<script setup>
const exampleData = inject('exampleData', 'default value here')
</script>
Working with Reactivity
- Options API
In order to make injections reactively linked to the provider, we need to provide a
computed property using the computed()
function:
<script>
import { computed } from 'vue'
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
return {
// explicitly provide a computed property
message: computed(() => this.message)
}
}
}
</script>
- Composition API
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:
Provide:
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
Injection:
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
Read Only Provider
You can wrap the provided value with readonly() if you want to ensure that the 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>