Subscribe on changes!

Rendering discrepancy between components written in JS and SFC

avatar
Jun 30th 2021

Version

3.1.2

Reproduction link

https://jsfiddle.net/3udtwzae/

https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHRlbXBsYXRlPlxuICA8b3B0aW9ucyA6b3B0aW9ucz1cIm9wdGlvbnNMaXN0XCIgLz5cbiAgPGhyIC8+XG4gIDxvcHRpb25zLXNldHVwIDpvcHRpb25zPVwib3B0aW9uc0xpc3RcIiAvPlxuPC90ZW1wbGF0ZT5cblxuPHNjcmlwdCBzZXR1cD5cbiAgaW1wb3J0IE9wdGlvbnMgZnJvbSAnLi9PcHRpb25zLnZ1ZSdcbiAgaW1wb3J0IE9wdGlvbnNTZXR1cCBmcm9tICcuL09wdGlvbnNTZXR1cC52dWUnXG4gIFxuICBjb25zdCBvcHRpb25zTGlzdCA9IFsnZm9vJ11cbjwvc2NyaXB0PiIsIk9wdGlvbnMudnVlIjoiPHRlbXBsYXRlPlxuICA8cHJlPnt7IG9wdGlvbnMgfX08L3ByZT5cbjwvdGVtcGxhdGU+XG5cbjxzY3JpcHQ+XG4gIGltcG9ydCB7IGNvbXB1dGVkIH0gZnJvbSAndnVlJ1xuICBcbiAgZnVuY3Rpb24gdXNlT3B0aW9ucyhwcm9wcykge1xuICAgIGNvbnN0IG9wdGlvbnMgPSBjb21wdXRlZCgoKSA9PiBwcm9wcy5vcHRpb25zLm1hcChvID0+ICh7XG4gICAgICBsYWJlbDogbyxcbiAgICAgIHZhbHVlOiBvLFxuICAgIH0pKSlcbiAgICBcbiAgICByZXR1cm4geyBvcHRpb25zIH1cbiAgfVxuICBcbiAgZXhwb3J0IGRlZmF1bHQge1xuICAgIHByb3BzOiB7XG4gICAgICBvcHRpb25zOiB7IHR5cGU6IEFycmF5LCByZXF1aXJlZDogdHJ1ZSB9XG4gICAgfSxcbiAgICBzZXR1cChwcm9wcykge1xuICAgICAgY29uc3QgbyA9IHVzZU9wdGlvbnMocHJvcHMpXG4gICAgICBcbiAgICAgIHJldHVybiB7XG4gICAgICAgIC8vIHBhc3NlZCBkaXJlY3RseSB3b3JrcyEhISEhXG4gICAgICAgIC8vIG9wdGlvbnM6IG8ub3B0aW9ucyxcbiAgICAgICAgLi4ubyxcbiAgICAgIH1cbiAgICB9XG4gIH1cbjwvc2NyaXB0PiIsIk9wdGlvbnNTZXR1cC52dWUiOiI8dGVtcGxhdGU+XG4gIDxwcmU+e3sgb3B0aW9ucyB9fTwvcHJlPlxuPC90ZW1wbGF0ZT5cblxuPHNjcmlwdCBzZXR1cD5cbiAgaW1wb3J0IHsgY29tcHV0ZWQgfSBmcm9tICd2dWUnXG4gIFxuXHRjb25zdCBwcm9wcyA9IGRlZmluZVByb3BzKHtcbiAgICBvcHRpb25zOiB7IHR5cGU6IEFycmF5LCByZXF1aXJlZDogdHJ1ZSB9XG4gIH0pXG4gIFxuICBjb25zdCBvcHRpb25zID0gY29tcHV0ZWQoKCkgPT4gcHJvcHMub3B0aW9ucy5tYXAobyA9PiAoe1xuICAgIGxhYmVsOiBvLFxuICAgIHZhbHVlOiBvLFxuICB9KSkpXG48L3NjcmlwdD4ifQ==

Steps to reproduce

<template>
  <pre>{{ options }}</pre>
</template>

<script>
  import { computed } from 'vue'
  
  function useOptions(props) {
    const options = computed(() => props.options.map(o => ({
      label: o,
      value: o,
    })))
    
    return { options }
  }
  
  export default {
    props: {
      options: { type: Array, required: true }
    },
    setup(props) {
      const o = useOptions(props)
      
      return {
        // passed directly works!
        // options: o.options,
        ...o,
      }
    }
  }
</script>

What is expected?

In both cases should render pre tag with content "[{"label":"foo","value":"foo"}]"

What is actually happening?

SFC (Options.vue) renders component's prop options instead of the value of the computed property with the same name when is not directly exposed in the setup's return statement but is rather spread from composable function.

avatar
Jun 30th 2021
// Options.vue
setup(props) {
    const { options } = useOptions(props)
    return {
        // passed directly works!!!!!
        // options: o.options,
        options,
  }
}

or just return options instead { options } on useOptions.

avatar
Jun 30th 2021

I know that now but why does it matter if I return a property XYZ directly or if I spread an object with a property XYZ? should it work the same in both cases? Shouldn't the setup context be taken into account first when accessing properties in the template? and why it currently works differently in the reproduction examples I provided.

avatar
Jun 30th 2021

I know that now but why does it matter if I return a property XYZ directly or if I spread an object with a property XYZ? should it work the same in both cases? Shouldn't the setup context be taken into account first when accessing properties in the template? and why it currently works differently in the reproduction examples I provided.

The samples are not the same, you are wrapping it { options }, you should return options to be the same.

avatar
Jun 30th 2021

but you are right, not working... working with:

function useOptions(props) {
    const options = computed(() => props.options.map(o => ({
      label: o,
      value: o,
    })))
    
    return options
  }
avatar
Jun 30th 2021

This is because you are using the same name for the computed and prop (options). Make sure to name it differently. It works with the script setup because it allows variable shadowing while the options do not. This will make more sense if you take a look at the generated JS for both Option components

avatar
Jun 30th 2021

@posva I am still kind of confused by this behavior. You're saying "Make sure to name it differently." but it works when I return computed named options directly in the setup. Shouldn't the component then render oprions from props (["foo"]) and not my computed property?

It works with the script setup ...

do you have <script setup> in mind? because I am talking about normal SFC component

avatar
Jun 30th 2021
function useOptions() {
  const options = computed(() => [])
  return { options }
}

export default {
  props: { options: Array },
  setup() {
    const { options } = useOptions()

    return {
      options
    }
  }
}

export default {
  props: { options: Array },
  setup() {
    return {
      ...useOptions()
    }
  }
}

in both cases I return options in the setup, both examples do the same thing but component works differently ¯_(ツ)_/¯ If the current behavior is correct maybe this needs to be somehow mentioned in the docs?

avatar
Jun 30th 2021

The thing is: you shouldn't name a computed like an existing prop. I'm referring to these:

Screen Shot 2021-06-30 at 15 52 54 Screen Shot 2021-06-30 at 15 52 57