Written by Michael Thiessen✏️
By design, the provide
and inject
features of Vue are not reactive, even though many people (myself included!) wish they were.
That’s not a huge problem for us though since there is a way to get around this:
If you create a reactive object using the data()
function or Vue.observable
, it will still be reactive when passed down using provide
and inject
.
It would look like this if you’re using the data function:
export default {
provide() {
return {
reactive: this.reactive
};
},
data() {
return {
reactive: {
value: "Hello there"
}
};
}
};
Now when reactive
is injected into another component, it will behave like any other prop and changes to reactive
will trigger updates in your Vue application!
TL;DR
In this article we’ll cover:
- Why the
data()
function is a great solution to this problem - The simplest method for making objects reactive, even outside of Vue
- A library by a Vue core team member that handles it all for you
- Questions to help you determine when it’s a good time to use reactive provide/inject
- Why you should be careful to avoid two-way data binding
But first, a warning
This should only be used in special circumstances. This technique should not be used as a replacement for props and events. If all you need is a basic way to pass data around, props and events are great, simple, and everyone else who reads your code will understand what’s going on.
So when should you use it?
We’ll cover that a little later on, but first, let’s take a look at three different ways we can make provide and inject reactive.
Using the data() function
The first technique is to make the object reactive by initializing it in our data()
function:
export default {
provide() {
return {
reactive: this.reactive
};
},
data() {
return {
reactive: {
value: "Hello there"
}
};
}
};
Any object that is initialized here will be made reactive by Vue. Once it is reactive, it is reactive no matter where it gets used.
You can even pass the object to some random JavaScript outside of your Vue app, and any changes made to the reactive object will trigger updates inside of your Vue app.
There is a second way we can make our object reactive, something that’s fairly new to Vue.
Using Vue.observable
Vue 2.6 introduced the observable
function that lets us create our own reactive objects:
import Vue from 'vue';
const state = Vue.observable({ message: "Hello!" });
In fact, this is the same function that Vue uses internally with the data()
function, but here it is exposed to us so that we can use it wherever we want.
In Vue 3, this function will be renamed to reactive
with the addition of the Composition API.
Let’s rewrite the previous example to use Vue.observable
:
import Vue from 'vue';
const reactive = Vue.observable({ value: 'Hello there' });
export default {
provide() {
return {
reactive: reactive,
};
},
};
This function gives us a lot more flexibility in how we create our reactive objects since we are no longer reliant on the data
function.
However, in most cases, you’ll be using the previous method because the state that you’re providing will typically already be initialized by the data()
function. But this is a great tool to add to your toolbox in case you ever find that the data()
function just doesn’t quite do what you need it to.
The final method we’ll cover is not a native Vue feature, but a mixin created by Vue core team member Linus Borg.
Linus Borg’s mixin
Linus, who is a member of the Vue core team, has created the vue-reactive-provide
mixin, which you can find on Github.
It’s a super easy way to make provide/inject reactive if you don’t like the other options, and there are two main ways you can use it.
Component option
If you want it to feel similar to the native provide
, you just have to install it as a plugin:
import Vue from 'vue';
import ReactiveProvide from 'vue-reactive-provide';
Vue.use(ReactiveProvide);
You would add this to your main.js
file if you’re using Vue CLI.
Then in the component where you want to provide the value, you would use reactiveProvide
instead:
export default {
reactiveProvide: {
name: 'injectedName',
include: ['reactive'],
}
data() {
return {
reactive: 'hello',
};
},
};
You need to give your reactive object a name
so we know what value to inject in the child. Then you can include any number of fields that you want using the include
array.
To inject this reactive object into a component you would inject it like normal, using the name
value you set earlier:
export default {
inject: ['injectedName']
};
Mixin
Using it as a mixin is nearly the same process, but you don’t need to register it as a plugin first:
import { ReactiveProvideMixin } from 'vue-reactive-provide'
export default {
mixins: [
ReactiveProvideMixin({
name: 'injectedName',
include: ['reactive'],
})
],
data() {
return {
reactive: 'hello',
};
},
};
We use the ReactiveProvideMixin
function to dynamically create a mixin. The mixin will provide the included value in a reactive way for us.
To inject the value, we use the exact method as before:
export default {
inject: ['injectedName']
};
When to use reactive provide/inject
In general, you should try to avoid using provide/inject, and instead pass data around using props and events. That will get you where you need to go most of the time, and you avoid adding unnecessary complexity and keep your code understandable.
However, there are a few specific questions you can ask yourself when deciding whether or not you should use this feature:
- Do you need to avoid prop drilling? — It can get tedious to pass a prop down through layer after layer of components, especially when those intermediate components don’t actually use the prop. This can also be solved by Vuex, but you sometimes want a simpler and lighter solution
- Are the components tightly coupled? — If you have a set of components that will always be used together, then it’s okay to rely on provide/inject to pass some of the data around. Otherwise, it’s best to stick with props and events, which is what most other components will be using
- Is Vuex overkill? — Vuex is a great tool, but if you’re dealing with something that has a simple state, then Vuex is overkill and introduces a lot of overhead. If the set of components needs to be reusable, then coupling it with Vuex also introduces unnecessary complexity
- Is the data contained within a few components? — If the data being passed around is only used by a couple components, then this solution can make sense. But if the data being used here is used elsewhere, keeping it higher up in the tree or using Vuex can be better solutions
Your use case doesn’t have to pass all of these rules, but it should fit at least one or two of them.
For example, let’s imagine you were working on an app that had fairly straightforward state. Vuex would be overkill, so you’ve decided not to use it.
Each page loads the user’s information in a user
object, and that object is used in all sorts of places all over the app. Displaying the user’s name, their email address, and other information like that is not limited to a specific part in the application.
To avoid passing this data as prop through every single component in our app, we provide
it at the top-level component, so any component that needs it can inject
the user
object and gain access to it directly.
Now, this might seem to violate our fourth rule, but it hits #1 and #3 right on the head. So this ends up being a great solution.
Before we wrap up, there’s one other thing that you should be sure to avoid when using this technique.
Avoid two-way data binding
When you use a reactive injection like this, a common mistake is to treat it as a two-way binding, where the value can be changed by the component providing it, as well as the one injecting it.
But this is a horrible idea? Only the component that is providing the value should ever be allowed to modify it.
This is for two main reasons:
- Vue uses a one-way binding system where data flows down the tree. Using a different data flow model in one spot of your app will make it inconsistent and will cause a lot of confusion
- Keeping all of the mutations in a single component makes it much easier to update, refactor, and track down bugs in the future
Conclusion
As we saw, it is possible to make provide and inject reactive, and it doesn’t take too much effort. There are three different ways to do it, so you’ll be able to find something that fits your project.
This technique is really useful, but as I mentioned, it can also make your code unnecessarily complicated. It’s best to stick with regular props and events if that will work for you.
If props and events aren’t working well, we went through a few different questions you can ask yourself to determine if this technique is right for your use case.
Lastly, we covered what two-way data binding is and why you should avoid it.
Editor's note: Seeing something wrong with this post? You can find the correct version here.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Try it for free.
The post How to make provide/inject reactive appeared first on LogRocket Blog.
Top comments (0)