DEV Community

Cover image for Leverage `provide/inject` to avoid prop drilling in Vue.js
Jonathan Bakebwa
Jonathan Bakebwa

Posted on

Leverage `provide/inject` to avoid prop drilling in Vue.js

This post was originally posted on my personal blog. 😀

There is no absolute right way of doing things. In the end we are all just monkeys with keyboards writing software that we hope will work for other monkeys too.

Table of Contents

Working with props.

Out of the box, Vue.js gives us the ability to pass data from a parent component to it's children using props. This makes it a lot easier to share information from a parent to it's child components.

Props can be both static, and dynamic(and/or reactive). This means that when the value of a prop that is passed from a parent to a child component changes, the prop value in the child updates as well and triggers a re-render for that component.

There are also instances when you need to share some values in a parent component with a (for lack of a better word) grandchild component. To solve this, one could use props to pass them down to the child and then the child component would eventually pass them down to it's grandchild component. However this is not very elegant and results in prop drilling which can be difficult to maintain for large applications.

Vue's provide / inject API.

In order to help prevent the prop drilling phenomenon, Vue.js also allows us to expose or provide variables in the parent component, that any child component in it's component tree depth can inject into it's context.

Vue uses these two properties combined to allow an ancestor component to serve as a dependency injector for all of it's descendants in the same parent chain. This opens up some really cool possibilities. Now, regardless of how deep the component hierarchy is, any descendant component can inject variables provided by an ancestor component into it's own context.

provide API

In order to make an ancestor component provide some variables to it's children, we use the provide property in the said component. The provide option can be an object or a function that returns an object.


// Provider.js

export default {
  name: 'Provider',
  provide() {
    return {
      $colorMode: 'light'
    }
  }
}

inject API

In the Child component that we wish to use/consume the variables provided by our Provider component, we can use the inject property. The inject option can either be:

  • an array of strings, or
  • an object where the keys are the local binding name and the value is either:

// Child.js

export default {
  name: 'Child',
  inject: ['$colorMode'],
  created () {
    console.log(this.$colorMode) // => "light"
  }
}

Cool! Now we have the $colorMode available in the Child component.

Let's look at a Real World Example to illustrate this.

Themed Component Library with provide and inject.

A lot of component libraries that have themes that require that the theme object is made available any where in the Vue application. This theme can be used to determine the colors for any given color mode. We'll also need to know the color mode of the application that the users prefer.

In this example, we'll create an tiny component library in Vue that has a light and dark color modes, and we use the current color mode to determine the colors of a descendant button component which exists at a much lower location in the component tree heirarchy.

Themed App using provide and inject

All code can be found in this codesandbox

1. ThemeProvider component.

We start by making a ThemeProvider.vue component to provide two variables that we will need, namely:

  • $theme - This is the global app theme object with color variables from our design system
  • $colorMode - This is the current application color mode that the user prefers.

I prefer to prefix provided variables with the $ so as to prevent namespace clashing in consumer components. It's easier for me to distinguish injected variables from local component variables.

This is what the ThemeProvider looks like:

<script lang="js">
export default {
  name: "ThemeProvider",
  props: {
    theme: {
      type: Object,
      default: () => null
    },
    colorMode: {
      type: String,
      default: "light"
    }
  },
  /*
   * Here we provide the theme and colorMode we received
   * from the props
   */
  provide() {
    return {
      $theme: () => this.theme,
      $colorMode: () => this.colorMode
    };
  },
  render() {
    return this.$slots.default[0];
  }
};
</script>

Because this component doesn't render anything in the DOM, we don't need to have a template so we make it a renderless component

2. Button consumer component

As the user toggles the color mode between light and dark, we need to inject the changed values in the button so as to reflect the corresponding theme styles accordingly. To do that we create a Button.vue component.


<script lang="js">
export default {
  name: "Button",
  inject: ["$theme", "$colorMode"],
  computed: {
    theme() {
      return this.$theme();
    },
    colorMode() {
      return this.$colorMode();
    }
  }
};
</script>

In our Button.vue component we use a computed variable in order to preserve the reactivity of the variables provided by the ThemeProvider.vue component.

Hooray! With any luck, you should be seeing these changes in your child component as well. For a more fully fleshed out example of how you can use provide/inject, here's a codesandbox example.

When to use provide & inject

In most applications, you will most probably not need to use the provide/inject features in Vue. A lot of the problems that it solves can be easily solved with proper state management using Vuex, or even props.

provide and inject are primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code.
~ Official Vue.js Docs


Thank you for reading!

It's my first time writing on DEV and I'd like to improve my writing as well as my knowledge. I'll be happy to receive your feedback and hopefully answer some questions about provide and inject 🖖🏽

Oldest comments (3)

Collapse
 
yellow1912 profile image
yellow1912

Can you somehow make inject dynamic? Let's say you have a listing component with dynamic templates (perhaps using inline-template): one listing one you may want to inject some onclick handler, on listing 2 you want to in an additional on hover handler etc...

Collapse
 
grawl profile image
Даниил Пронин

Just solved this problem with this: github.com/egoist/vue-provider

I am using Quasar Framework and running now in SSR + PWA mode, all provided props is reactive.

Collapse
 
grawl profile image
Даниил Пронин

Oh no, it calls re-render for any changes. And I cannot figure out why.