Dynamic component doesn't display anything in Vue3? - vue.js

I have a dynamic component in my project like that can be "Login" or "RegisterForm"
But it doesn't display anything.
<script setup lang="ts">
import { ref } from "vue";
import RegisterForm from '#/components/Layout/Navbar/Register.vue'
import Login from '#/components/Layout/Navbar/Login.vue'
let activeComponent = ref('Login')
</script>
<template>
<button #click="activeComponent='RegisterForm'">change to register</button>
<component :is="activeComponent" />
</template>
How can I fix this?

I'm not sure if this is the exact best way to write that in Composition API but this works without any error/warning.
<script setup>
import { shallowRef } from "vue";
import CompA from "#/components/CompA.vue";
import CompB from "#/components/CompB.vue";
const activeComponent = shallowRef(CompA);
</script>
<template>
<button #click="activeComponent = CompB">toggle to Component B</button>
<component :is="activeComponent" />
</template>

Related

vue3 js component :is not changing component

I have a component which gathers data from an API. The data brought back from the API is an array with details in. One of the values in the array is the type of component which should be rendered, all other data is passed through to the component.
I'm trying to render the correct component based of the value brought back from the database, but it is sadly not working.
I'm new to Vue but had it working in vue2 but would like it to work in Vue 3 using the composition API.
this is my component code which I want to replace:
<component :is="question.type" :propdata="question" />
When viewed within the browser this is what is actually displayed, but doesn't use the SelectInput component:
<selectinput :propdata="question"></selectinput>
SelectInput is a component with my directory, and works as intended if I hard code the :is value, like below:
<component :is="SelectInput" propdata="question" />
my full component which calls the component component and swaps components:
<template>
<div class="item-group section-wrap">
<div v-bind:key="question.guid" class='component-wrap'>
<div class="component-container">
<!-- working -->
<component :is="SelectInput" :propData="question" />
<!-- not working -->
<component v-bind:is="question.type" :propData="question" />
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, toRefs } from 'vue';
import SelectInput from "./SelectInput";
import TextareaInput from "./TextareaInput";
const props = defineProps({
question: Object,
});
const { question } = toRefs(props);
</script>
In case that your Components filenames are equal to the type specifier on your question Object, then you could dynamically import them to save some code lines.
This would also result in better scalability since you don't have to touch this component anymore in case you create more types.
<template>
<div class="item-group section-wrap">
<div v-bind:key="question.guid" class='component-wrap'>
<div class="component-container">
<!-- working -->
<component v-bind:is="getComponent(question.type)" :propData="question" />
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, toRefs } from 'vue';
const props = defineProps({
question: Object,
});
// GIVEN THE question.types are equal to the fileNames of the components to render:
const getComponent = (name) => import(`./${name}.vue`);
const { question } = toRefs(props);
</script>
Figured out that because I'm using script setup the components aren't named so the component component doesn't know which component to render.
so, i created an object with the components and a reference to the component:
const components = {
'SelectInput': SelectInput,
'TextareaInput': TextareaInput
};
and another function which takes in the component i want to show and links it to the actual component:
const component_type = (type) => components[type];
then in the template i call the function and the correct component is rendered:
<component v-bind:is="component_type(question.type)" :propData="question" />
complete fixed code:
<template>
<div class="item-group section-wrap">
<div v-bind:key="question.guid" class='component-wrap'>
<div class="component-container">
<!-- working -->
<component v-bind:is="component_type(question.type)" :propData="question" />
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, toRefs } from 'vue';
import SelectInput from "./SelectInput";
import TextareaInput from "./TextareaInput";
const props = defineProps({
question: Object,
});
const components = {
'SelectInput': SelectInput,
'TextareaInput': TextareaInput
};
const component_type = (type) => components[type];
const { question } = toRefs(props);
</script>
Not too sure if this is the correct way of doing this but it now renders the correct component.

Vue 3: component `:is` in for loop fails

I'm trying to loop over a list of component described by strings (I get the name of the component from another , like const componentTreeName = ["CompA", "CompA"].
My code is a simple as:
<script setup>
import CompA from './CompA.vue'
import { ref } from 'vue'
// I do NOT want to use [CompA, CompA] because my inputs are strings
const componentTreeName = ["CompA", "CompA"]
</script>
<template>
<h1>Demo</h1>
<template v-for="compName in componentTreeName">
<component :is="compName"></component>
</template>
</template>
Demo here
EDIT
I tried this with not much success.
Use resolveComponent() on the component name to look up the global component by name:
<script setup>
import { resolveComponent, markRaw } from 'vue'
const myGlobalComp = markRaw(resolveComponent('my-global-component'))
</script>
<template>
<component :is="myGlobalComp" />
<template>
demo 1
If you have a mix of locally and globally registered components, you can use a lookup for local components, and fall back to resolveComponent() for globals:
<script setup>
import LocalComponentA from '#/components/LocalComponentA.vue'
import LocalComponentB from '#/components/LocalComponentB.vue'
import { resolveComponent, markRaw } from 'vue'
const localComponents = {
LocalComponentA,
LocalComponentB,
}
const lookupComponent = name => {
const c = localComponents[name] ?? resolveComponent(name)
return markRaw(c)
}
const componentList = [
'GlobalComponentA',
'GlobalComponentB',
'LocalComponentA',
'LocalComponentB',
].map(lookupComponent)
</script>
<template>
<component :is="c" v-for="c in componentList" />
</template>
demo 2
Note: markRaw is used on the component definition because no reactivity is needed on it.
When using script setup, you need to reference the component and not the name or key.
To get it to work, I would use an object where the string can be used as a key to target the component from an object like this:
<script setup>
import CompA from './CompA.vue'
import { ref } from 'vue'
const components = {CompA};
// I do NOT want to use [CompA, CompA] because my inputs are strings
const componentTreeName = ["CompA", "CompA"]
</script>
<template>
<h1>Demo</h1>
<template v-for="compName in componentTreeName">
<component :is="components[compName]"></component>
</template>
</template>
To use a global component, you could assign components by pulling them from the app context. But this would require the app context to be available and the keys known.
example:
import { app } from '../MyApp.js'
const components = {
CompA: app.component('CompA')
}
I haven't tested this, but this might be worth a try to check with getCurrentInstance
import { ref,getCurrentInstance } from 'vue'
const components = getCurrentInstance().appContext.components;

importing a component in vue 3

Hi I'm trying to import a component in Vue 3.
This is my component structure :
+src
+App.vue
+components
+Navbar.vue
In my App.vue I tried :
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div>
<Navbar/>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue';
</script>
This is my main.ts :
import { createApp } from 'vue'
import router from './router'
import mitt from 'mitt';
const emitter = mitt();
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'
import BootstrapVue3 from 'bootstrap-vue-3'
import App from './App.vue'
const app = createApp(App)
app.use(router)
app.use(BootstrapVue3)
app.mount('#app')
app.config.globalProperties.emitter = emitter;
But I get this error :
Already included file name '/home/jip/Projects/snippedit/client/src/components/Navbar.vue' differs from file name '/home/jip/Projects/snippedit/client/src/components/navBar.vue' only in casing.
The file is in the program because:
Imported via './components/Navbar.vue' from file '/home/jip/Projects/snippedit/client/src/App.vue'
Root file specified for compilation
Root file specified for compilation
Does anyone know a solution to this problem?
Thanks in advance.
That's how App.vue should look like:
<template>
<div>
<Navbar />
</div>
</template>
<script setup>
import Navbar from './components/Navbar.vue';
</script>
If you are using vuter extension try restarting/reloading vscode

Vue 3, $emit never firing

The documentation is not enough to be able to do the emit. I have seen many tutorials and nothing works, now I am testing this
Child component
<div #click="$emit('sendjob', Job )"></div>
With the Vue DevTools plugin I can see that the data is sent in the PayLoad, but I can't find a way to receive this emit from the other component.
Many people do this
Any other component
<template>
<div #sendjob="doSomething"></div>
</template>
<script>
export default {
methods:{
doSomething(){
console.log('It works')
}
}
}
</script>
In my case it doesn't work
You should import the child component in the parent component and use it instead of the regular div tag.
I'm sharing examples for your reference to achieve emits in Vue 3 using <script setup> and Composition API. I strongly suggest going with <script setup if you are going to use Composition API in Single File Component. However, the choice is yours.
Example with <script setup>: https://v3.vuejs.org/api/sfc-script-setup.html
<!-- App.vue -->
<template>
<UserDetail #user-detail-submitted="userDetailSubmitted"/>
</template>
<script setup>
import UserDetail from './components/UserDetail';
function userDetailSubmitted(name) {
console.log({ name })
}
</script>
<!-- UserDetail.vue -->
<template>
<input type="text" v-model="name" #keyup.enter="$emit('user-detail-submitted', name)" />
</template>
<script setup>
import { ref } from 'vue';
const name = ref('');
</script>
Example using Composition API: https://v3.vuejs.org/api/composition-api.html
<!-- App.vue -->
<template>
<UserDetail #user-detail-submitted="userDetailSubmitted"/>
</template>
<script>
import UserDetail from "./components/UserDetail";
export default {
components: {
UserDetail,
},
setup() {
function userDetailSubmitted(name) {
console.log({ name });
}
return {
userDetailSubmitted
}
},
};
</script>
<!-- UserDetail.vue -->
<template>
<input type="text" v-model="name" #keyup.enter="$emit('user-detail-submitted', name)" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const name = ref('');
return {
name,
}
}
}
</script>
You should import this child-component in the parent. And don't rename it to the html's original tag.vue3. You'd better use the Composition API.

No export statement in App.vue, but App module is imported in main.ts. How is this possible?

In the example router project generated by Vue CLI, I don't see export statement in App.vue. But App module is found imported in main.ts. How the import of a module is made possible without an export statement?
But I see the export statement in App.vue of a non-router example project.
main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
createApp(App)
.use(router)
.mount("#app");
App.vue of router example project
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view />
</template>
<style>
...
</style>
App.vue of a non-router example project
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "./components/HelloWorld.vue";
#Options({
components: {
HelloWorld
}
})
export default class App extends Vue {}
</script>
<style>
...
</style>