DEV Community

loading...

Pass all props to children in Vue

jwkicklighter profile image Jordan Kicklighter Updated on ・2 min read

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Thanks to @morficus for pointing this out!

Discussion (25)

Collapse
morficus profile image
Maurice Williams

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.

Collapse
jwkicklighter profile image
Jordan Kicklighter Author

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!

Collapse
jwkicklighter profile image
Jordan Kicklighter Author • Edited

Sure enough, it does! I don't think I tried both v-bind="$props" andv-bind="$attrs"`, which is the trick. I'll update this post!

Collapse
nesterow profile image
Anton Nesterov

Thanks! Following also works:
v-bind="{...$props, ...$attrs}"

Thread Thread
jwkicklighter profile image
Jordan Kicklighter Author

That's a great space saver! Thanks 🙂

Collapse
malinyamato profile image
Malin Yamato Lääkkö • Edited

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?

Collapse
jwkicklighter profile image
Jordan Kicklighter Author

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 has 1 prop called "prop"
  • Component B has 2 props, "prop" and "childOnly"
  • Component B is a child of Component A
  • We put down and instance of Component A like so: <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!

Collapse
malinyamato profile image
Malin Yamato Lääkkö

,,,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. :)

Thread Thread
jwkicklighter profile image
Jordan Kicklighter Author

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.

Collapse
abrahambrookes profile image
Abraham Brookes

You can also pass all events back up by giving your wrapped component a little shake of v-on="$listeners" ;)

Collapse
jwkicklighter profile image
Jordan Kicklighter Author

That's great for this use case! Thanks for the tip :)

Collapse
hjames profile image
Harry J Beckwith

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

Collapse
jwkicklighter profile image
Jordan Kicklighter Author

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.

Collapse
malinyamato profile image
Malin Yamato Lääkkö

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.

Thread Thread
jwkicklighter profile image
Jordan Kicklighter Author

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.

Thread Thread
maxart2501 profile image
Massimo Artizzu

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.

Thread Thread
jwkicklighter profile image
Jordan Kicklighter Author

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.

Collapse
anduser96 profile image
Andrei Gatej

Thanks for sharing!

I suggest taking a look at Vue.observable, you might find it useful. This could also solve your problem

Collapse
jwkicklighter profile image
Jordan Kicklighter Author

Hmm, I'm curious where that would fit in for this particular flow.

Collapse
anduser96 profile image
Andrei Gatej

You're right. It might not be suitable for this problem.

I didn't take all the cases into consideration.. :D

Sorry!

Thread Thread
jwkicklighter profile image
Jordan Kicklighter Author

No worries! Always great to hear some other options that might be useful 😊

Thread Thread
anduser96 profile image
Andrei Gatej

Also I didn’t know about what $attrs exactly does, so thanks for sharing!

Collapse
gypsydave5 profile image
David Wickes • Edited

'Pass all props to children' sounds like a school play

Collapse
tserkov profile image
James

Or, use the recommend Vue way, provide/inject: vuejs.org/v2/guide/components-edge...

Collapse
daneren2005 profile image
Scott

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.

Forem Open with the Forem app