June 14, 2023
vue.js - provide - inject - web development - frontend
5 minutes

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.

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:

Imgur

Notice although the <Footer> component may not care about these props at all, it still needs to declare and pass them along just so 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.

Imgur

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>