Display Component from an array of components - vue.js

I want to find component from a list of components(or list of names of components) by name without loading components. Eg:
const arr = [
<ComponentA />,
<ComponentB />
]
const selectedComponent = arr.find(component => component.name === 'ComponentA');
and then render this selectedComponent in a div.
<div>{selectedComponent}</div>
**Note: ** Each component has unique name.

You could use a dynamic component instead it will load your select component name like this example
<component :is="component_name"></component>
and inside your data add the variable
data(){
return {
component_name: 'home'
}
}
For more info check the docs here

Related

Vue does not get template ref of vNode-rendered elements array with v-for

I'm creating a library and I do renderless components that just renders whatever is in slots. For the purpose of demonstration, I added dummy VNodes array instead.
<template>
<component
v-for="vnode of vnodes"
:key="vnode.toString()"
:is="vnode"
ref="elements"
/>
</template>
<script setup>
import { ref, h, onMounted } from "vue";
const elements = ref([]);
const vnodes = [
h(
"div",
{
class: "foo"
},
"Hello One!"
),
h(
"div",
{
class: "bar"
},
"Hello Two!"
)
];
onMounted(() => {
console.log(elements.value[0]);
// expected: First element of array (Element)
// reality: undefined
});
</script>
CodePen repro
In my use case, I fetch VNodes from slots and then rendering it via <component> in template. It renders properly, however I can't get the references to rendered elements at onMounted(). Does anyone know what's wrong with this approach, or is it a Vue bug?
I have tried it in codepen and I also think this is how you should do it in vue 3. (best practice as of Vue.js version 3)
Functional Template Refs
:ref="(el) => elements.push(el)"
instead of
ref="elements"
If you face issues with duplicates in the array you might have to reset the array in a vue lifecycle hooks

How to get slot props with render functions in Vue?

I'm trying to transform a Vue component I've made in a render function. Problem is I can't find a way to access the named slot props, like in the example:
<template #slot-name="slotProps">
<MyComponent v-bind="slotProps" />
</template>
There's a way to transform this code in a render function ?
To pass a scoped slot, use the scopedSlots property of the 2nd argument to h() (createElement()) in the form of { name: props => VNode | Array<VNode> }.
For example, assuming your template is:
<MySlotConsumer>
<template #mySlot="slotProps">
<MyComponent v-bind="slotProps" />
</template>
</MySlotConsumer>
The equivalent render function would be:
export default {
render(h) {
return h(MySlotConsumer, {
scopedSlots: {
mySlot: slotProps => h(MyComponent, { props: slotProps })
}
})
}
}
demo

Dynamic component inside a component that is rendered with render() function

I saw in the docs that you can have a dynamic component inside your VueComponent like this :
<component :is="componentName" v-bind="componentProps" #custom-event="doSomething" />
I am trying to have one of these dynamic components inside a dynamically rendered component (with the render() function, not with an HTML template). Without too much hope, I've tried this :
render(createElement: CreateElement) {
return createElement('component', props: {
'is': 'TestComponent'
});
}
but I got
[Vue warn]: Unknown custom element: <component> - did you register the component correctly?
So again, not hoping too much for a miracle, I tried to import Component and declare it as a component :
#Component({
components: {
Component,
TestComponent
}
})
export default class DynamicThingy extends Vue {
render(createElement: CreateElement): VNode {
return createElement('Component', {
props: {
'is': 'TestComponent'
}
});
}
}
But then I get
[Vue warn]: Do not use built-in or reserved HTML elements as component id: Component
Any idea how that could be possible ?
The first parameter of createElement() must be either
An HTML tag name,
component options,
or async function resolving to one of these.
https://v2.vuejs.org/v2/guide/render-function.html#createElement-Arguments
So in a render function, you can simply create a function* that returns one or another component's options based on your desired criteria, and let that be your first argument. *This function is identical to the function you'd write to determine what goes into the :is prop)
You only need the dynamic component <component /> and :is prop in a template where you don't have the possibility to do it programmatically.
You can use this smart-list component from the vue docs as an example.

Vue - conditionally pass down props

I'd like to know if there is a good way to conditionally pass down one or more props to a child component.
If there is sometimes an id set, then I want to pass it down. I can't set id to null because a prop must have a value. I have solved it before by using a "v-if" like this:
<survey-wrapper v-if="id" :inputJson="inputJson" :id="id"></survey-wrapper>
<survey-wrapper v-else :inputJson="inputJson"></survey-wrapper> // <-- no id
But it's a workaround which looks bad and it becomes a lot of code if the component has many props. And what if you have two props that may be set or not?
You can use v-bind and pass it and object containing all your props. and conditionally add your id prop like this.
<survey-wrapper v-bind="{ inputJson, ...(id ? { id } : {}) }"></survey-wrapper>
You can do it using v-bind and dynamically creating the list of props with a method.
Here is an example:
<template>
<div id="app">
<Component v-bind="propsToPass()"/>
</div>
</template>
<script>
export default {
name: "App",
components: {
Component
},
data() {
return {
passId: true
}
},
methods: {
propsToPass() {
const result = {};
if (this.passId) {
result.id = 1;
}
return result
}
}
};
</script>
In the above example, the prop id will only be passed if passId is true.
I think the best way to do so is by using v-bind with a logical operator && :
<survey-wrapper :inputJson="inputJson" v-bind="id && {id}" />
note that it will only pass the prop if (id) is available in such case id should not be required as a prop by the component.
Thanks

vuejs - Async AND Dynamic component

What I want to achive is to combine vuejs's "Async Components" and its "Dynamic Components" (https://v2.vuejs.org/v2/guide/components-dynamic-async.html) to get a flexible way of using dynamic components.
Let's say, I have the following component:
<template>
<div>
some stuff..
<component :is="type"></component>
some stuff...
</div>
</template>
<script>
export default {
name: "mycomponent",
props: {
type: {}
}
}
</script>
I would use it like so:
<mycomponent type="myinput"></mycomponent>
To make it work, I would need to load the myinput component in the mycomponent and I will do it aync:
components: {
MyInput: () => import("./myinput")
}
That works!
The problem now is, that I want to make it dynamic and use it like so:
<mycomponent type="myinput"></mycomponent>
<mycomponent type="myselect"></mycomponent>
<mycomponent type="mytextarea"></mycomponent>
...
To get this up and running, I would need to import all of this components in the mycomponent, which is obviously not an good idea. Global import is as well, not the way to go.
This was just an example, but the real use case for this is getting an array of type's from an api and dynamically render a form with it.
So I have came up with I idea what I could not try yet.
Lets say you have a component for different types of inputs for the sake of simplicity you name them all with an 'Input' prefix.
As long as everyone follows the naming rules you can register all of these component globally.
For example in a BaseInputs.js
const components = require.context(path, subfolder, regexForInputComponentFileNames)
https://webpack.js.org/guides/dependency-management/#require-context
components.keys().forEach(element => {
const componentName = element.replace(/*Everything that is not the component name like .vue*/)
Vue.component(componentName, () => import(path + componentName)
})
Then you just import 'path/to/BaseInputs' at the beginning of your application.