Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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"
                class="w-full mx-2 pr-2"
                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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

<template>
    <div>
        <ul
            class="tag-menu flex space-x-2"
            :class="defaultTagMenu ? 'default' : 'historic'"
            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"
                :class="{ selected: title === selectedTitle }"
            >
                <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" class="mt-4">
        <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.

>Solution :

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)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading