Leverage provide/inject
to avoid prop drilling in Vue.js
Published on 2020-01-28
Table of Contents
- Working with Props
- What is
provide
&inject
? - Real World Example
- When to use
provide
&inject
- Conclusion
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.jsexport 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.jsexport 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.
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!