Home > Enterprise >  Using v-for inside component that uses <slot/> to create re-usable and dynamic Tab menu
Using v-for inside component that uses <slot/> to create re-usable and dynamic Tab menu

Time:04-23

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)

  • Related