Home > Net >  Vue 3 - use props string to load component
Vue 3 - use props string to load component

Time:01-17

I have following component:

<script setup>

import {computed, onMounted, ref, watch} from "vue";
import {useDialogStore} from "@/store/dialog";
import TableSwitcher from "@/components/Dialogs/Components/TableSwitcher.vue"

let emit = defineEmits(['confirmDialogConfirmed', 'confirmDialogClose'])
let dialogStore = useDialogStore()

let question = computed(() => dialogStore.dialogQuestion)
let mainComponent = ref('')

let props = defineProps({
  open: {
    type: Boolean,
    required: true
  },
  id: {
    type: String,
    default: 'main-dialog'
  },
  component: {
    type: String,
    required: true,
  }
})

watch(props, (newValue, oldValue) => {

    mainComponent.value = props.component

    console.log(mainComponent);

      if(newValue.open === true)
      {
        dialog.showModal()
      }

    },
    {
      deep:true
    }
);

let dialog = ref();

let closeDialog = (confirmAction = false) =>
{
  dialog.close()
  dialogStore.close(confirmAction)
}

onMounted(() => {
  dialog = document.getElementById(props.id);
});

</script>

<template>
  <dialog :id="id">
    <component :is="mainComponent" ></component>
  </dialog>
</template>

For activating component I am using this:

<main-dialog
    v-if="component"
    :component="component"
    :open="true">
</main-dialog>

component value is created on click and passed as a prop to the main component. When I click to activate this component I am getting following error:

Invalid vnode type when creating vnode

When I hard code the component name for the mainComponent var the component is loaded correctly. What am I doing wrong here?

CodePudding user response:

There are different ways to solve that. I think in your case it would make sense to use slots. But if you want to keep your approach you can globally define your components in your Vue app.

without slots

const app = createApp({});

// define components globally as async components:
app.component('first-component', defineAsyncComponent(async () => import('path/to/your/FirstComponent.vue'));
app.component('second-component', defineAsyncComponent(async () => import('path/to/your/SecondComponent.vue'));

app.mount('#app');

Then you can use strings and fix some bugs in your component:

  • Don’t use ids, instead use a template ref to access the dialog element
  • Use const instead of let for non-changing values.
  • props are already reactive so you can use the props also directly inside your template and they will be updated automatically when changed from the outside.
// inside <script setup>

import {computed, onMounted, ref, watch} from "vue";
import {useDialogStore} from "@/store/dialog";
import TableSwitcher from "@/components/Dialogs/Components/TableSwitcher.vue"

// use const instead of let, as the values are not changing:
const emit = defineEmits(['confirmDialogConfirmed', 'confirmDialogClose'])

const props = defineProps({
  open: {
    type: Boolean,
    required: true
  },
  id: {
    type: String,
    default: 'main-dialog'
  },
  component: {
    type: String,
    required: true,
  }
});

const dialogStore = useDialogStore()
const question = computed(() => dialogStore.dialogQuestion);
const dialog = ref(null);

watchPostEffect(
  () => {
    if(props.open) {
      dialog.value?.showModal()
    }
  },
  // call watcher also on first lifecycle:
  { immediate: true }
);

let closeDialog = (confirmAction = false) => {
  dialog.value?.close()
  dialogStore.close(confirmAction)
}

<!-- the sfc template -->
<dialog ref="dialog">
  <component :is="props.component" />
</dialog>

with slots

<!-- use your main-dialog -->
<main-dialog :open="open">
  <first-component v-if="condition"/>
  <second-component v-else />
</main-dialog>
<!-- template of MainDialog.vue -->
<dialog ref="dialog">
  <slot />
</dialog>
  • Related