So I'm trying to create a dynamic tab menu with Vue 3 and slots. I got the tabs working, I have BaseTabsWrapper and BaseTab components. I need to be able to v-for with BaseTab component inside of a BaseTabsWrapper Component. Like this:
<section
id="content"
v-if="incomingChatSessions && incomingChatSessions.length"
>
<BaseTabsWrapper>
<BaseTab
v-for="chatSession in incomingChatSessions"
:key="chatSession.id"
:title="chatSession.endUser.name"
>
<p>{{ chatSession }}</p>
</BaseTab>
</BaseTabsWrapper>
</section>
An important caveat from the answers that I have found is that the incomingChatSessions object is asynchronous and coming from a websocket (I have tested that this object is working fine and bringing all the data correctly aka is never an empty object).
Inside of BaseTabsWrapper template. Important parts:
<template>
<div>
<ul
:
role="tablist"
aria-label="Tabs Menu"
v-if="tabTitles && tabTitles.length"
>
<li
@click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
:title="title"
role="presentation"
:
>
<a href="#" role="tab">
{{ title }}
</a>
</li>
</ul>
<slot />
</div>
</template>
And the script:
<script>
import { ref, useSlots, provide } from 'vue'
export default {
props: {
defaultTagMenu: {
type: Boolean,
default: true,
},
},
setup(props) {
const slots = useSlots()
const tabTitles = ref(
slots.default()[0].children.map((tab) => tab.props.title)
)
const selectedTitle = ref(tabTitles.value[0])
provide('selectedTitle', selectedTitle)
provide('tabTitles', tabTitles)
return {
tabTitles,
selectedTitle,
}
},
}
</script>
This is the Tab component template:
<template>
<div v-show="title === selectedTitle" >
<slot />
</div>
</template>
<script>
import { inject } from 'vue'
export default {
props: {
title: {
type: String,
default: 'Tab Title',
},
},
setup() {
const selectedTitle = inject('selectedTitle')
return {
selectedTitle,
}
},
}
</script>
The important part in my script and the one that is giving me a lot of trouble is this one:
const tabTitles = ref(
slots.default()[0].children.map((tab) => tab.props.title)
)
What I'm doing here is creating an array of tab titles based on the property "title" of each slot but when I load the page this array always have just one title, even if I'm fetching more title elements from the API. One thing that I have noticed is that if I force a re-render of the page from my code then the tabTitles array have the correct amount of elements and I got all the correct amount of tabs on my menu. I have tested that everything is working fine with the way I control asynchronicity with the data coming from the websocket in order to hidrate the "incomingChatSessions" array but as much as I try tabTiles always gets just one element no matter what.
CodePudding user response:
i would do something like that :
computed(
() => slots.default()[0].children.map((tab) => tab.props.title)
)
it should update the computed property when the component is updated (like slot changes)