Vue: Generate default prop value based on value of another prop

Created on 13 Aug 2017  Â·  13Comments  Â·  Source: vuejs/vue

What problem does this feature solve?

I have been recently making a Vue component library which has a dozen of props. There are two props called showCount and showCountOnSearch respectively. The props property of my component looks like:

export default {
  props: {
    showCount: {
      type: Boolean,
      default: false,
    },
    showCountOnSearch: {
      type: Boolean,
      default: false,
    },
    // ...other props
  },
  // ...
}

I want the default value of showCountOnSearch to be determined based on the value of showCount. For example:

  • When showCount is not specified, set showCount to the default value false. If showCountOnSearch is not specified either, set it to false too.
  • When showCount is specified and showCountOnSearch is not, set showCountOnSearch to the same value of showCount.

In other words, showCountOnSearch always defaults to the value of showCount.

What does the proposed API look like?

Currently Vue allows default property to be functions to generate default value dynamically. However, seems it's impossible to obtain another prop's value inside the default function.

I hope the API to look like:

export default {
  props: {
    showCount: {
      type: Boolean,
      default: false,
    },
    showCountOnSearch: {
      type: Boolean,
      default(props) {
        return props.showCount
      },
    },
    // ...other props
  },
  // ...
}

Most helpful comment

I think computed is not an ideal solution because it introduces the need of making up a new name that makes the code harder to reason about (conceptually the name of the prop externally and the name of the computed property internally are identical). It also makes vue component props less flexible than javascript function parameter defaults (which can be based on one another).

Because of this we end up with multiple patterns to do the same thing throughout our codebase (prop: initialFoo, computed: Foo or foo, computedFoo, theFoo, foo etc. many of which end up being of each other which makes references in the template (of the component and its parent) confusing. We can come up with a pattern and adopt it, but none are particularly ideal (bc they add conceptual overhead to a behaviour that is more straightforward than implied by the necessary implementation).

I appreciate this isn't really possible in Vue as-is, but I think the pattern is an important one and wonder if it could have support from the library either as an official recommendation (in the styleguide) or code support (something like a computedDefaultProps block inside components (or inside the computed block itself to avoid polluting the top level of component scope) that creates computed functions that override the prop values while still remembering what they are without the need for new names... )

All 13 comments

@riophae, you can achieve same by using a computed prop like this:

export default {
  props: {
    showCount: {
      type: Boolean,
      default: false,
    },
    showCountOnSearch: {
      type: Boolean,
    },
    // ...other props
  },
  computed: {
    showCountOnSearchComputed () {
      return this.showCountOnSearch || this.showCount
    }
  }
}

We cannot define props based on other props directly in the default value because the order in which props are resolved will decide that default value. However, because props is an object, there's no order in keys. Thus, making props' value depend on each other can lead to unexpected behaviour.

What you can do instead is:

  • Use a computed property
  • Add a property in data to hold the actual variable and change it in the created hook

Or combine both if necessary

Yeah, computed property solves the problem.
But actually it's possible to determine the order:

var propsWithDefaultFunction = []
var propsWithDefaultFunctionThatReliesOnOthers = []
var otherProps = []

for (var key in props) {
  var prop = props[key]
  if (typeof prop === 'object' && typeof prop.default === 'function') {
    if (prop.default.arguments.length === 0) {
      propsWithDefaultFunction.push(key)
    } else {
      propsWithDefaultFunctionThatReliesOnOthers.push(key)
    }
  } else {
    otherProps.push(key)
  }
}

// first resolve default values for `otherProps`
otherProps.forEach(...)
// then resolve default values for `propsWithDefaultFunction`
propsWithDefaultFunction.forEach(...)
// at last resolve default values for `propsWithDefaultFunctionThatReliesOnOthers`
propsWithDefaultFunctionThatReliesOnOthers.forEach(...)

@riophae, what if b relies on a and then c relies on b?

@nickmessing yeah... that's impossible.

@riophae, that's actually possible using Proxy and calling everything with fake objects but it's not reasonable and limits browser support., computed properties approach works great in all scenarios.

I think computed is not an ideal solution because it introduces the need of making up a new name that makes the code harder to reason about (conceptually the name of the prop externally and the name of the computed property internally are identical). It also makes vue component props less flexible than javascript function parameter defaults (which can be based on one another).

Because of this we end up with multiple patterns to do the same thing throughout our codebase (prop: initialFoo, computed: Foo or foo, computedFoo, theFoo, foo etc. many of which end up being of each other which makes references in the template (of the component and its parent) confusing. We can come up with a pattern and adopt it, but none are particularly ideal (bc they add conceptual overhead to a behaviour that is more straightforward than implied by the necessary implementation).

I appreciate this isn't really possible in Vue as-is, but I think the pattern is an important one and wonder if it could have support from the library either as an official recommendation (in the styleguide) or code support (something like a computedDefaultProps block inside components (or inside the computed block itself to avoid polluting the top level of component scope) that creates computed functions that override the prop values while still remembering what they are without the need for new names... )

To follow up... our team has adopted the pattern of calling the prop foo and the computed property _foo. it matches the javascript private/public convention nicely. Reference _foo inside the component, reference foo externally.

I also saw somewhere someone reporting that it is possible to return a function based on other props to get a default value, and it does work sometimes.. but like @posva mentioned i think this will unpredictable. is it based on the order in which the props are passed into the component therefore really **ing dangerous!? 😲 .. might warrant an associated console warning ... 🤔

This would work if validation happens after the first pass (https://github.com/vuejs/vue/issues/9467)

@posva
Would be nice if this was in the docs here. Thank you!
https://vuejs.org/v2/guide/components-props.html

We cannot define props based on other props directly in the default value because the order in which props are resolved will decide that default value. However, because props is an object, there's no order in keys. Thus, making props' value depend on each other can lead to unexpected behaviour.

What you can do instead is:

  • Use a computed property
  • Add a property in data to hold the actual variable and change it in the created hook

Or combine both if necessary

Even computed may work for the default values but I have the same issue for the validator. What I needed was to validate a prop based on some other props. How this can be achieved?

You would have to do that in a lifecycle hook in pretty sure. That’s the
best I could come up with anyway 😕

On Thu, Oct 22, 2020 at 19:29 btalebpour notifications@github.com wrote:

Even computed may work for the default values but I have the same issue
for the validator. What I needed was to validate a prop based on some other
props. How this can be achieved?

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/6358#issuecomment-714816823, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/AAKPYIY7U7OKIXIAH42TWZLSMC56DANCNFSM4DWWJASA
.

>

Damon Muma

Yes, I understand and it was on my mind but the behavior won't be the same.
Thanks anyway.

On Fri, Oct 23, 2020 at 9:59 AM Damon Muma notifications@github.com wrote:

You would have to do that in a lifecycle hook in pretty sure. That’s the
best I could come up with anyway 😕

On Thu, Oct 22, 2020 at 19:29 btalebpour notifications@github.com wrote:

Even computed may work for the default values but I have the same issue
for the validator. What I needed was to validate a prop based on some
other
props. How this can be achieved?

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/6358#issuecomment-714816823, or
unsubscribe
<
https://github.com/notifications/unsubscribe-auth/AAKPYIY7U7OKIXIAH42TWZLSMC56DANCNFSM4DWWJASA

.

>

Damon Muma

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/6358#issuecomment-715460630, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/AA6DI6SOM5ZZ4ERLOV2NNZ3SMGY7PANCNFSM4DWWJASA
.

--
Behzad

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aviggngyv picture aviggngyv  Â·  3Comments

fergaldoyle picture fergaldoyle  Â·  3Comments

bfis picture bfis  Â·  3Comments

franciscolourenco picture franciscolourenco  Â·  3Comments

6pm picture 6pm  Â·  3Comments