This article is also posted on my blog.
While brainstorming some new Vue components, I wanted to have a way that a single wrapper component could fetch all of the data needed for the children views, and pass that data through (around 5 layers of) children down to the the bottom.
So I did some experimenting, and here’s a way to pass all component props down through child components. Link to codepen.
Vue actually makes this very easy for us. All component props are available in the $props
object (this.$props
if you're not in the template), and we can use the Vue directive v-bind
without specifying a specific prop name in order to bind a whole object of props to the child.
Example
Let's assume we have some wrapper component that is going to render a child comp1
component and pass it both propForComp1
and propForComp2
as props.
Here's Comp1.vue
:
<template>
<div class="comp1">
<span>{{ propForComp1 }}</span>
<comp2 v-bind="$props" />
</div>
</template>
<script>
export default {
components: Comp2,
props: [
'propForComp1',
'propForComp2'
],
}
</script>
Notice that comp1
is only using propForComp1
and doesn't really care about propForComp2
. But since it needs to be included in the props passed to comp2
, propForComp2
still needs to be declared inside the props
object. If not, $props
will not contain it.
You can do this for many levels of components, but remember that the props any child needs must be declared in all parents. So if you have 5 layers, propForComp5
must be declared as a prop in prop1
, prop2
, prop3
, prop4
, and prop5
. You can make this a little easier by using a Mixin, which is the route I took in the codepen.
UPDATE: You actually don't have to do this last bit! Just like Vue gives up $props
, the $attrs
object includes all of the passed attributes that the component doesn't declare as props. This means, that we can simply use v-bind="$attrs"
to pass down the attributes that children would care about, even if the component doesn't specify them itself.
The previous example would turn into:
<template>
<div class="comp1">
<span>{{ propForComp1 }}</span>
<comp2 v-bind="$props" v-bind="$attrs" />
</div>
</template>
<script>
export default {
components: Comp2,
props: [
'propForComp1'
],
}
</script>
A diff to see the changes:
<template>
<div class="comp1">
<span>{{ propForComp1 }}</span>
- <comp2 v-bind="$props" />
+ <comp2 v-bind="$props" v-bind="$attrs" />
</div>
</template>
<script>
export default {
components: Comp2,
props: [
'propForComp1',
- 'propForComp2'
],
}
</script>
Thanks to @morficus for pointing this out!
Top comments (25)
I sometimes forget about $props in Vue. Your technique seems particularly handy when many "layers" need to reuse the same prop.
In your example above the parent component needed to define a prop only for the sake of passing it down. That can be annoying really quick... One suggestion I have to avoid the redeclaration of props on every level is binding $attrs the same way you're doing with $props.
I tried that, but it didn't seem to work for me (unless I did something wrong, which is quite likely). I'll try it again based on how you described it and see if that works because it would be much preferred to my method!
Sure enough, it does! I don't think I tried both
v-bind="$props" and
v-bind="$attrs"`, which is the trick. I'll update this post!Thanks! Following also works:
v-bind="{...$props, ...$attrs}"
That's a great space saver! Thanks 🙂
Thank you for explaining this.
Why do you need both v-bind="$props" and v-bind="$attrs" ? Why not only v-bind="$attrs"? Isnt v-bind="$attrs" taking care of what v-bind="$props" takes care of as well?
Hey Malin, that's a great question! In Vue,
$attrs
is an object containing all attributes that are not declared as props.$props
contains the attributes that are declared as props. So you must bind both if there are both prop values and non-prop values that you need to pass to the child.For example:
<component-a :prop="propVal" :child-only="childOnlyVal" />
In that example, if we inspect the instance of Component A, we will see:
$props: { prop: propVal }
$attrs: { childOnly: childOnlyVal }
This is because childOnly is not declared as a prop in the definition of Component A.
Hopefully that makes sense!
,,,think I got it thanks to your reply -- so, the parent only regards its own declared props as props and puts them into $props. All other arguments, which are passed into to parent but unknown by the parent, are dumped into $attr. :)
Yep, that's a good way to put it! Your origin question is how I assumed Vue worked, but after working on the sample in the Codepen I realized that $attrs and $props were separate.
To avoid tracking props through many different layers which can become overly complex, I would suggest using Vuex and directly accessing the reactive state through getters, actions and mutations. I wrote a basic article about this here medium.com/js-dojo/vuex-2638ba4b1d76
Vuex is a wonderful piece of tech that I enjoy using, but sometimes it isn't the right answer for an app. For instance, my main app uses a different type of data store, so this sort of architecture makes more sense. Additionally, sometimes the component architecture makes more sense to pass props down to children rather than accessing everything from the child. That gives more flexibility to refactor the Vuex store structure without having to change the accessors in every component.
TL;DR Vuex is a valuable tool, but not for every single use case.
Kinda OT! I have little experience in web front-ends, I am a server/side girl> perhaps I am an old schooler from the CORBA TIBCO-Rendezvous ICE era, but why use VUEX to keep the data/state in the browser when you may be better of maintaining the state/data in the server through AJAX or axios calls and let components/subcomponents who want data "real-time" listen (subscribe) to data in the server though websockets? Or is VUEX supposed to be a layer between components and server, thus having no actual data stored in the browser.
You're sort of correct about Vuex sitting between the browser and server, although the specific way an app uses it depends on the app's design. But as a simple answer: Vuex does not store persistent state, it is only storing runtime state. So if you pull data from a server, you can put it in Vuex as a very easy way for the components in your application to access it. If you need components to communicate with each other, these sorts of things can also go through Vuex. A very simple example is a global variable for "menu open vs closed" stored in Vuex. The menu button can update this variable, and anything in the app that needs to respond to the variable changes can do so.
The topic of why to use Vuex (or any flux system, such as redux or mobx) is a complicated one, so I'd encourage you to read the Vuex docs and various blogs about it. The docs have some examples that will likely help shine some light on how Vuex is used.
This question opens a lot of interesting perspectives.
Today it sounds naive, but it's good to recall certain things... and also reconsider some aspects.
In short, communication with the server is slow. I can spend a millisecond to store some data in the browser, but at best it's 20 to store them in the server. But it could be 200, depending on the size of the data... Or 2000 or worse if the connection is flaky.
This could lead to a very bad user experience.
So, you could store your state data in the server, but it's preferable if you do that as a backup: nice to have, because you could restore the session even from a different browser, but not fundamental, as the application is usable offline too.
This is sort of an interesting take, and I think it's addressing a different type of data than my other comment. UI state data should be stored locally, in Vuex or through some other means. However, application data (e.g. the contacts in a contact app, the documents in a note-taking app, etc.) need persistent storage that is maintained between page loads. If you refresh the page, your Vuex state is gone. It starts from scratch Everytime the Vue app is bootstrapped.
The most common way of persisting data is to use a backend that takes care of storage, which also has the benefit of allowing access to that data from multiple places. If you don't want to use a server, there are options such as localstorage, sessionstorage, IndexdDB, and more that will keep your data local. But it's important to note that these options and the server option are not for UI state, they are for long-lived data. In all of these situations, Vuex is loading this long-term data from somewhere else, even if not a server.
Vuex itself is not a database or a persistent data Storage layer, but it can be a good place to store UI state and a helpful place to cache your persistent data.
You can also pass all events back up by giving your wrapped component a little shake of
v-on="$listeners"
;)That's great for this use case! Thanks for the tip :)
Thanks for sharing!
I suggest taking a look at
Vue.observable
, you might find it useful. This could also solve your problemHmm, I'm curious where that would fit in for this particular flow.
You're right. It might not be suitable for this problem.
I didn't take all the cases into consideration.. :D
Sorry!
No worries! Always great to hear some other options that might be useful 😊
Also I didn’t know about what
$attrs
exactly does, so thanks for sharing!Or, use the recommend Vue way, provide/inject: vuejs.org/v2/guide/components-edge...
Reading the docs those aren't reactive which is a huge reason to use Vue in the first place. For some functions that return never changing data it's great but not for passing properties down to children.
'Pass all props to children' sounds like a school play