Subscribe on changes!

<component> is not replaced when using v-bind={is: ...}

avatar
Sep 30th 2020

Version

3.0.0

Reproduction link

https://codesandbox.io/s/kind-fire-2p58g?file=/src/App.vue

Steps to reproduce

Open the Link, see the underlined error / Its not running

What is expected?

To be able to bind is with the v-bind="obj" syntax

What is actually happening?

It only works with a separate is attribute

avatar
Sep 30th 2020

Replacement can only work reliably if the presence of the is attribute is guaranteed. That is not the case when you are using the define object syntax, as that by nature means that there is in dynamic JavaScript expression involved, which during runtime, may or may not return an object with an is property.

While we could make the compiler create the necessary dynamic component resolution code whether or not this risk exists, I don't think it makes sense to support this kind of foot gun.

Right now, the compiler expects the presence of that is attribute in order to successfully transform this piece of template into a dynamic component resolution. What we could do, is to warn in the console when the is property is not statically defined.

avatar
Mar 16th 2021

This problem lost me an entire afternoon. I was trying the same in the vue 2 codepen for the same feature and it was working, so it ended up being a really frustrating day.

It might help someone who will have the same problem in the future:

Because I was iterating an array (json schema to generate inputs) and calling a method on its elements to determine the component name and its props, I really needed this feature, because I was not going to process the same algorithm for every property needed for the component.

In the end I created a new component with a sole tag with an explicit "is" and the rest of the properties separated. I then use a computed prop to run my algorithm passing the prop (json schema object) as the algorithm starting point.

avatar
Dec 10th 2021

fixed this problem this way:

    <component
        v-bind="(({ is, ...o }) => o)(componentOptions)"
        :is="componentOptions.is"
    </component>

yes, it looks hacky but it works and this way is won't show up in the rendered HTML as an attribute on the rendered component.

avatar
Dec 10th 2021

I think you ate killing some performance there because that function will lead to unnecessary rerendering. Why not just v-bind="{...obj, is: undefined}"?

avatar
Dec 10th 2021

that will still render on the html is="undefined"

but yeah didn't check its performance, if it's true what you say then I suppose the best option is to create another computed property without the is attribute

avatar
Dec 10th 2021

@kepi0809 you sure? Afaik, undefined props are skipped

avatar
Dec 10th 2021

@kepi0809 you sure? Afaik, undefined props are skipped

weird... this was the first approach I tried to fix this problem and it didn't seem to work then, but now I tested it again and it works with both is: null and is: undefined.

Thank you for for pointing again in that direction :+1:

avatar
Sep 27th 2022

This problem lost me an entire afternoon. I was trying the same in the vue 2 codepen for the same feature and it was working, so it ended up being a really frustrating day.

It might help someone who will have the same problem in the future:

Because I was iterating an array (json schema to generate inputs) and calling a method on its elements to determine the component name and its props, I really needed this feature, because I was not going to process the same algorithm for every property needed for the component.

In the end I created a new component with a sole tag with an explicit "is" and the rest of the properties separated. I then use a computed prop to run my algorithm passing the prop (json schema object) as the algorithm starting point.

met the same problem, and is there any solution, my friend? try several ways and only fixed the props as a computed data could meet the demand.

<template>
  <KeepAlive>
    <!-- it works -->
    <Component :is="currentTab.component" v-bind="props" />
    <!-- it does not work -->
    <Component :is="currentTab.component" v-bind="currentTab.props" />
  </KeepAlive>
</template>

<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { ITabType } from "@/components/Tabs.vue";
import { Channel, Complay } from "@/UnionTask";

enum ETab {
  channel,
  complay,
}

interface ITabConfig extends ITabType<ETab> {
  component?: typeof Vue;
  props?: Record<string, any>;
}

@Component({
  components: {
    Channel,
    Complay,
  },
})
export default class ChannelUnion extends Vue {
  unionInfo: Record<string, string> = {}; // union info

  channelListInUnion: Record<string, string>[] = []; // channel list

  currentTab: ITabConfig = this.tabConfig?.[0];

  /**
   * @description
   * Dynamic parameters of dynamic components
   * Because tabConfig is an array props cannot be declared directly in the object,
   * an additional dynamic props must be maintained independently
   * @see https://github.com/vuejs/core/issues/2279#issuecomment-800608949
   */
  get props() {
    if (this.currentTab.value === ETab.channel) {
      return {
        unionInfo: this.unionInfo,
        channelListInUnion: this.channelListInUnion,
      };
    }
    return {};
  }

  get tabConfig(): ITabConfig[] {
    return [
      {
        value: ETab.channel,
        label: "channel task",
        component: Channel,
        props: {
          unionInfo: this.unionInfo,
          channelListInUnion: this.channelListInUnion,
        },
      },
      {
        value: ETab.complay,
        label: "complay task",
        component: Channel,
      },
    ];
  }

  onTabChange(tab: ITabConfig) {
    this.currentTab = tab;
  }
}
</script>

<style lang="scss" scoped></style>