skip to Main Content

I’m just learning Vue and trying to learn dynamic rendering using the component tag. What is the problem with this code? No errors are displayed in the console, but clicking on the buttons still does not display the required component. It does work with v-if, but the point of this lecture I’m following is to use the component dynamic rendering. Which does not work:

<template>
  <div>
    <the-header></the-header>
    <button @click="setSelectedComponent('active-goals')">Active goals</button>
    <button @click="setSelectedComponent('manage-goals')">Manage goals</button>
    <component :is="selectedTab"></component>
  </div>
</template>

<script setup>
/* eslint-disable no-unused-vars */
import { ref, defineExpose, defineComponent } from 'vue';
import TheHeader from './components/TheHeader.vue';
import ActiveGoals from './components/ActiveGoals.vue';
import ManageGoals from './components/ManageGoals.vue';

const selectedTab = ref('active-goals');
const setSelectedComponent = (tab) => {
  selectedTab.value = tab;
};

defineExpose({
  selectedTab,
  setSelectedComponent,
});

defineComponent({
  components: {
    TheHeader,
    ActiveGoals,
    ManageGoals,
  },
});
</script>

<style>
html {
  font-family: sans-serif;
}

body {
  margin: 0;
}
</style>

Thanks for any help!

4

Answers


  1. In script setup the imported components are treated as variables because they’re not registered under keys as we do in components option in non script setup syntax, so you can map them with keys inside an object :

    <template>
      <div>
        <the-header></the-header>
        <button @click="setSelectedComponent('ActiveGoals')">Active goals</button>
        <button @click="setSelectedComponent('ManageGoals')">Manage goals</button>
        <component :is="selectedTab"></component>
      </div>
    </template>
    
    <script setup>
    /* eslint-disable no-unused-vars */
    import { ref, defineExpose, defineComponent } from 'vue';
    import TheHeader from './components/TheHeader.vue';
    import ActiveGoals from './components/ActiveGoals.vue';
    import ManageGoals from './components/ManageGoals.vue';
    
    const selectedTab = ref(ActiveGoals);
     const components = {
        ActiveGoals,
        ManageGoals,
      }
    const setSelectedComponent = (tab) => {
      selectedTab.value = components[tab];
    };
    
    defineExpose({
      selectedTab,
      setSelectedComponent,
    });
    
    
    
    </script>
    
    Login or Signup to reply.
  2. UPDATE 2

    Playground with the tao’s solution


    UPDATE

    Answering the question from tao:

    To make <component :is=""> work with strings, you have to register you components with names.


    You don’t need defineComponent in this case.

    Check the original Vue Solution for Tabs in the Dynamic Components

    Working Playground with your code

    Here is you fixed code:

    <script setup>
    /* eslint-disable no-unused-vars */
    import { ref, defineExpose, defineComponent } from 'vue';
    import TheHeader from './TheHeader.vue';
    import ActiveGoals from './ActiveGoals.vue';
    import ManageGoals from './ManageGoals.vue';
    
    const selectedTab = ref('active-goals');
    const setSelectedComponent = (tab) => {
      selectedTab.value = tab;
    };
    
    const tabs = {
      'active-goals': ActiveGoals,
      'manage-goals': ManageGoals
    }
    
    </script>
    
    <template>
        <the-header></the-header>
        selectedTab: {{selectedTab}}<br/>
        <button @click="setSelectedComponent('active-goals')">Active goals</button>&nbsp;
        <button @click="setSelectedComponent('manage-goals')">Manage goals</button>&nbsp;
        <hr/>
        <component :is="tabs[selectedTab]"></component>
    </template>
    
    Login or Signup to reply.
  3. for this to work you can register your components globally, for Vue to make the link with your selected component. Otherwise, you can use the defineAsync method from Vue.

    registerGlobally

    defineAsync

    1:
    your .vue

    <template>
      <div>
        <the-header></the-header>
        <button @click="setSelectedComponent(ActiveGoals)">Active goals</button>
        <button @click="setSelectedComponent(ManageGoals)">Manage goals</button>
        <component :is="selectedTab"></component>
      </div>
    </template>
    
    <script setup>
    /* eslint-disable no-unused-vars */
    import { ref, defineExpose, defineComponent } from 'vue';
    import TheHeader from './components/TheHeader.vue';
    
    const selectedTab = ref('active-goals');
    const setSelectedComponent = (tab) => {
      selectedTab.value = tab;
    };
    
    defineExpose({
      selectedTab,
      setSelectedComponent,
    });
    
    defineComponent({
      components: {
        TheHeader,
      },
    });
    </script>
    

    In your main

    import { createApp } from 'vue';
    import App from './App.vue';
    import ActiveGoals from './components/ActiveGoals.vue';
    import ManageGoals from './components/ManageGoals.vue';
    
    const app = createApp(App);
    app.component('active-goals', ActiveGoals)
    app.component('manage-goals', ManageGoals)
    app.mount('#app');
    

    2:
    your .vue

    <template>
      <div>
        <the-header></the-header>
        <button @click="setSelectedComponent('active-goals')">Active goals</button>
        <button @click="setSelectedComponent('manage-goals')">Manage goals</button>
        <component :is="selectedTab"></component>
      </div>
    </template>
    
    <script setup>
    /* eslint-disable no-unused-vars */
    import { ref, defineExpose, defineComponent } from 'vue';
    import TheHeader from './components/TheHeader.vue';
    import { defineAsyncComponent } from 'vue'
    
    const ActiveGoals = defineAsyncComponent(() =>
      import('./components/ActiveGoals.vue')
    )
    const ManageGoals = defineAsyncComponent(() =>
      import('./components/ManageGoals.vue')
    )
    
    const selectedTab = ref(ActiveGoals);
    const setSelectedComponent = (tab) => {
      selectedTab.value = tab;
    };
    
    defineExpose({
      selectedTab,
      setSelectedComponent,
    });
    
    defineComponent({
      components: {
        TheHeader,
      },
    });
    </script>
    
    Login or Signup to reply.
  4. You can’t use strings with :is if the components are not registered.

    You either use the components themselves instead of the strings (@Tolbxela’s or @Bussadjra’s answers), or register the components.
    And you can’t register the components (easily) in <script setup>, because <script setup> syntax is limited. It’s literally a macro to write:

    <script>
    import { defineComponent } from 'vue'
    export default defineComponent({
      setup() {
        // CODE HERE
      }
    })
    

    as:

    <script setup>
    // CODE HERE
    </script>
    

    … with some mods to allow imports inside the setup() function and few more other goodies (defineProps, defineEmits, etc…).

    The limitation should be obvious: if you need anything besides the contents of setup() function from defineComponent(), you want to use a normal <script>.

    In other words, <script setup> should be treated as a tool to reduce boilerplate, you shouldn’t put effort into attempting to write all your components using it.

    Last, but not least, <script setup> can be used alongside a normal <script> tag. In your case:

    <script setup>
    const selectedTab = ref('active-goals');
    const setSelectedComponent = (tab) => {
      selectedTab.value = tab;
    };
    </script>
    
    <script>
    import { defineComponent } from 'vue'
    import TheHeader from './components/TheHeader.vue';
    import ActiveGoals from './components/ActiveGoals.vue';
    import ManageGoals from './components/ManageGoals.vue';
    export default defineComponent({
      components: {
        TheHeader,
        ActiveGoals,
        ManageGoals,
      },
    });
    </script>
    

    The normal <script> registers the components so <template> can translate the strings into actual components.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search