Vue Slot rendering name instead of content - vue.js

When I use one of the vue select components I've tried, something strange happens. Where the list values were supposed to appear instead I'm getting the slot names. This will be much clearer in the example.
I've tried using vue-multiselect, vue-select, & vue-cool-select and I've come to the conclusion that this is not a library problem, but vue's or maybe my configuration.
This example shows vue-multiselect but happens with every library I've used so far. It is the most basic example provided by the vue-multiselect documentation itself.
<template>
<div>
<multiselect
v-model="value"
:options="options"
:searchable="false"
:close-on-select="false"
:show-labels="false"
placeholder="Pick a value"
></multiselect>
</div>
</template>
<script>
export default {
data() {
return {
value: null,
options: ['Select option', 'options', 'selected', 'mulitple']
}
},
}
</script>
This is what I get:
https://i.imgur.com/mJjs6lU.png
Anyone got any idea why this might be happening?
Thank you!

Your code should be working fine with this code here.
<template>
<div>
<multiselect
v-model="value"
:options="options"
:searchable="false"
:close-on-select="false"
:show-labels="false"
placeholder="Pick a value"
></multiselect>
</div>
</template>
<script>
import Multiselect from "vue-multiselect";
export default {
name: "App",
components: {
Multiselect
},
data() {
return {
value: null,
options: ['Select option', 'options', 'selected', 'mulitple']
}
},
};
</script>

So I have a function in my project to handle translations called _t() which is also defined as a Vue.prototype._t(). And so it happens that Vue.prototype._t refers to the renderSlot() function.
Remember to never prototype Vue with a function called _t(). I lost nearly 4h until I tried commenting almost every line of code.

Related

vue single file components naming; is it important?

What's the point of the name of a single file vue component?
In this example:
<template>
<div class="inventory-section">
<draggable v-model="itemSectionProps.itemSectionCategory">
<transition-group>
<div
v-for="category in itemSectionProps.itemSectionCategory"
:key="category.itemSectionCategoryId"
>
<!-- <p>{{ category.itemSectionCategoryName }}</p> -->
<inventory-section-group :itemSectionGroupProps="category">
</inventory-section-group>
</div>
</transition-group>
</draggable>
</div>
</template>
<script>
import InventorySectionGroup from "./InventorySectionGroup";
import draggable from "vuedraggable";
export default {
name: "Inventory",
components: {
InventorySectionGroup,
draggable,
},
inventory section group is named like:
<script>
import InventoryItem from "./InventorySectionGroupItemC";
export default {
name: "Do I even have a point?",
components: {
InventoryItem,
},
props: {
itemSectionGroupData: {
type: Object,
},
},
};
</script>
so, does the name in the component matter?
After some testing, all components seem to work as long as they're translated from camel to kebap-case when imported (and used). Is this the case?
A good justification for the name is that lets say you have a naming convention to your files and for components.
For example if all components are named with what they are but not appended with comp (ie: Inventory.vue instead of InventoryComp.vue) and when you use them you want to be more explicit about what they are (components) so you want to use this component like this: <inventory-comp />. An easy way to do this is to use the name property and set it like this in your Inventory.vue:
name: 'InventoryComp` // or inventory-comp

Vue manually mounting & remounting components

I have the following stripped down code that dynamically mounts components from a dropdown list:
<template>
<v-app>
<v-container>
<v-layout>
<v-select label="Providers"
single-line
:items="providers"
item-text="txt"
item-value="val"
:v-model="provider"
v-on:change="setProvider" />
<div ref='provider' id='provider' />
</v-layout>
</v-container>
</v-app>
</template>
<script>
import Provider1 from './components/Provider'
import Provider2 from './components/Provider2'
import Vue from 'vue'
import vuetify from './plugins/vuetify';
export default {
data: () => {
return {
provider: null,
providers: [
{txt: 'a', val: Provider1},
{txt: 'b', val: Provider2}
],
};
},
methods: {
setProvider(val) {
console.log(this.$refs.provider);
if (this.provider) {
// unmount and/or re-create #provider dom element
}
this.provider = new (Vue.extend(val))({
vuetify,
}).$mount('#provider');
}
},
}
</script>
First selection works great, subsequent selections graces my console window with "[Vue warn]: Cannot find element: #provider"
What should be placed in // unmount and/or re-create #provider dom element?
Also, if these need to be separately created questions, let me know:
What happens to the dom element? It doesn't get replaced as console.log(this.$refs.provider); clearly shows.
Why is manually mounting components advised against everywhere by everyone? Pending info on the unmount code, this way of doing it looks much more elegant than a slough of v-ifs would look in my opinion.
(edit: added 3rd question)
Are there any downsides to mixing vanilla markup with Vuetify's such as the above <div />?
Thanks
(edit: revised, working code. I've added an emit for extra fun)
<template>
<v-app>
<v-app-bar app />
<v-main>
<v-select label="Providers"
:items="providers"
v-model="provider" />
<component :is="provider" #fb="feedback" />
</v-main>
</v-app>
</template>
<script>
import Provider1 from './components/Provider'
import Provider2 from './components/Provider2'
export default {
data: () => {
return {
provider: null,
providers: [
{text: 'a', value: Provider1},
{text: 'b', value: Provider2}
],
};
},
methods: {
feedback(v) {
alert(v);
}
}
}
</script>
If your objective is to change between components on-the-fly, you can use the is Vue keyword to build dynamic components. That way you won't need to use v-ifs to control which component must render.
I'm also pretty sure you're not supposed to $mount inside components I believe that causes some side-effects and isn't generally good practice, since there are at least other ways to do it.
About mixing Vuetify and vanilla HTML, there's mostly no problem there. Some of Vuetify's selectors are pretty specific (like using scrollable in a v-dialog with v-card) but most are more general.

Vue: can't use a component inside another compontent

I am trying to use a child compontent in another compontent and it does not work. I have been trying to solve this problem looking for typos etc. for hours, but can't find anything.
Menu.vue
<template>
<div class='navbar-and-alert'>
<alert/>
<nav class='navbar'>
</nav>
</div>
</template>
<script>
import Alert from './Alert.vue'
export default {
name: 'Navbar',
compontents: {
Alert
},
data (){
return {
}
},
}
</script>
Alert.vue
<template>
<section class='alert-section'>
<p class='alert-section__content'>
...
</p>
<a href=''><img src='/static/assets/img/close.svg' class='alert-section__close-icon'></a>
</section>
</template>
<script>
export default {
name: 'Alert',
}
</script>
I get this alert in console:
Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
The alert component works when used inside App.vue
components has a typo:
compontents: {
Alert
},
Should be:
components: {
Alert
},

What is wrong with my vue-multiselect options?

I have a vue-multiselect component in a form, and the console reports that my options are undefined, even though I can see they are not. My options are fetched from a back end and put in the store well before this component is created.
The console error is
Invalid prop: type check failed for prop "options". Expected Array, got Undefined
Here is my component
<template>
<form action="#" #submit.prevent>
<section>
<div class="container">
<h2 class="subtitle">Details</h2>
<b-field label="Role" horizontal>
<multiselect
:options="roleOptions"
track-by="id"
label="title"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
></multiselect>
</b-field>
</div>
</section>
</form>
</template>
<script>
import Multiselect from "vue-multiselect";
import { mapState, mapActions } from "vuex";
export default {
name: "ProcessDetailsComponent",
components: {
multiselect: Multiselect
},
computed: {
roleOptions() {
return this.$store.state.processes.formData.process_roles;
}
},
};
</script>
<style scoped>
</style>
In the developer tools Vue inspector, I can see that the options look correct (to me). I've tried passing them in as props, computed values, mapped state - same problem every time.
If I swap the options out for a static array, defined in the data() function, it works ok. Can anyone confirm that I am implementing this correctly?
:options="roleOptions" expects an array. Make sure the vue variable is an array and not an object or undefined etc as mentioned above.
data(){
return{
roleOptions:[],
}
}

Extending Vue.js SFC components

I haven't found a good resource on extending Vue.js components. In every project I've worked on, regardless of the UI component library that's used, there are application Base components which extend the UI library components to enforce company/application defaults and standards.
I'm trying to extend Vue-Multiselect: https://vue-multiselect.js.org/ which has about 30 props and 12 slots. The component I'm extending doesn't matter -- I only mention it because ideally I don't want to have to repeat 30 props and 12 slots in my implementation.
I simply want to make two changes to the behavior of the component:
Make disabled prop a bit smarter
The Vue-Multiselect component has a standard disabled prop which works as expected:
<Multiselect :disabled="isDisabled" ...>
In our application, we have global state in Vuex which determines if the application is read-only. What I want to avoid is requiring developers to pass this state to every form field:
<Multiselect :disabled="readOnly || isDisabled" ...>
<OtherComponent :disabled="readOnly || someOtherCondition" ...>
...
So the user of my base component should only need to be concerned about their local UI state which affect the disabled status:
<BaseCombo :disabled="!emailValid" ...>
This would handle the 90% case of form fields that are locked down when the application is read-only and I can use an additional prop for cases where we want to ignore the global read-only status.
<BaseCombo :disabled="!emailValid" :ignoreReadOnly="true" ...>
Provide defaults
Secondly, I simply want to override some of the default prop values. This post addresses the question of supplying defaults:
https://stackoverflow.com/a/52592047/695318
And this works perfectly until I tried to modify the behavior of the disabled prop I mentioned previously.
My attempt to solve this was to either wrap or extend the component. I'd really want to avoid redeclaring all of the props if possible.
<template>
<Multiselect
:disabled="myCustomDisabled"
:value="value"
#input="$emit('input', $event)"
:options="options"
:label="label"
:track-by="trackBy"
:placeholder="placeholder"
... repeat for all 30 options
<script>
import Multiselect from 'vue-multiselect'
export default {
name: "BaseCombo",
extends: Multiselect, // extend or simply wrap?
computed: {
myCustomDisabled() {
this.props.disabled || ... use disabled from Vuex state
}
},
props: {
disabled: Boolean,
placeholder: {
type: String,
default: 'My Default Value',
},
... repeat for all props
The problem I ran into is I don't know how to handle the slots. The user of this BaseCombo should still be able to use all 12 slots in the VueMultiselect component.
Is there a better solution for extending components?
You can use this.$props to access props defined in the props attribute. Similarly you can access attributes (things you haven't defined as props) with this.$attrs. Finally you can bind props with v-bind="someVariable".
If you combine this you can do something like this:
<!-- App.vue -->
<template>
<component-a msg="Hello world" :fancy="{ test: 1 }" />
</template>
<!-- ComponentA.vue -->
<template>
<component-b v-bind="$attrs" />
</template>
<script>
export default {
name: 'componentA'
}
</script>
<!-- ComponentB.vue -->
<template>
<div>
{{ msg }}
{{ fancy }}
</div>
</template>
<script>
export default {
props: {
msg: String,
fancy: Object
},
mounted () {
console.log(this.$props);
}
}
</script>
In this example, component B would be the component you try to extend.
Here's a complete example based on Sumurai8's answer and motia's comments.
<template>
<Multiselect v-bind="childProps" v-on="$listeners">
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
<slot :name="name" v-bind="slotData" />
</template>
</Multiselect>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
name: "BaseCombo",
props: {
placeholder: {
type: String,
default: 'This is my default',
},
disabled: {
type: Boolean,
default: false,
},
},
components: {
Multiselect,
},
computed: {
childProps() {
return { ...this.$props, ...this.$attrs, disabled: this.isDisabled };
},
appReadOnly() {
return this.$store.state.appReadOnly;
},
isDisabled() {
return this.disabled || this.appReadOnly;
}
},
}
</script>