Search code examples
javascriptvue.jsvue-composition-apivuejs-slots

How can a container change the display of a child in the default slots?


I have a vue playground.

The container component needs to be able to control the display status of an unknown number of children in the default slot.

I assume the child components will need to hold a property or have some way of identifying themselves to their container.

How can the container change the display property of one of the components in the default slot?

NOTE: there is a related question at How can a container change the CSS display property of a child in the default slots? with a focus on using CSS to resolve the same issue.

App.vue

<script setup>
import Container from './Container.vue'
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'
</script>

<template>
    <Container>
        <ChildA />
        <ChildB />
    </Container>
</template>

Container.vue

<script setup>
import { useSlots, useAttrs, onMounted} from 'vue'

const slots = useSlots()

function logSlots(where) {
  console.log( `${where} slots`, slots )
  const children = slots.default()
  console.log( `${where} slots children`, children.length, children )
}

logSlots("setup")

function changeDisplay(whichChild, show) {
  console.log( "change display", whichChild, show)
  // how can I know I am accessing child a?
  // what goes here?
}

onMounted( () => {
  logSlots("onMounted")
})
</script>

<template>
  <button @click="logSlots('button')">log slots</button>
  <button @click="changeDisplay('child a', false)">Hide Child A</button>
  <button @click="changeDisplay('child a', true)">Show Child A</button>

  <slot />
</template>

ChildA.vue

<script setup>
</script>

<template>
  <div>
    ChildA
  </div>
</template>

ChildB.vue

<script setup>
</script>

<template>
  <div>
    ChildB
  </div>
</template>

Solution

  • You can hide the default slot's children either by name or reference (also by index (not in the code)):

    VUE SFC PLAYGROUND

    <script setup>
    import { useSlots, reactive} from 'vue';
    import ChildA from './ChildA.vue'
    import ChildB from './ChildB.vue'
    const slots = useSlots();
    const slotted = () => slots.default().map(vnode => hide.has(vnode.type.name) ? null : vnode);
    const slotted2 = () => slots.default().map(vnode => hide.has(vnode.type) ? null : vnode);
    
    const hide = reactive(new Set);
    
    
    function changeDisplay(whichChild) {
      hide.has(whichChild) ? hide.delete(whichChild) : hide.add(whichChild);
    }
    
    </script>
    
    <template>
      <button @click="changeDisplay('ChildA')">Toggle Child A</button>
      <button @click="changeDisplay('ChildB')">Toggle Child B</button>
      <slotted />
      <button @click="changeDisplay(ChildA)">Toggle Child A</button>
      <button @click="changeDisplay(ChildB)">Toggle Child B</button>
      <slotted2 />
    </template>
    

    A dynamic example:

    VUE SFC PLAYGROUND