skip to Main Content

I am conditionally rendering a child component using render function. Here is an example of the parent component with the child component:

<Parent :index="1">
  <Child> ... </Child>
  <Child> ... </Child>
  <Child> ... </Child>
</Parent>

in the render function of Parent component, I have the following code to conditionally render the child component like this:

return () => {
  const content = slots.default?.();
  const children = content?.filter(child => child.type === Child);
  const childToRender = children?.[props.index] ?? h('div')
  return h('div', {}, () => [childToRender]);
}

the code is working as expected.

However, I would like to wrap one of the child component with another component. For example like this:

<Parent :index="1">
  <Child> ... </Child>
  <ChildWrapper> ... </ChildWrapper>
  <Child > ... </Child>
</Parent>

where ChildWrapper.vue looks like this:

<template>
  <Child> <slot/> </Child>
</template>

which evidently the filter function (content?.filter(child => child.type === Child)) will not pick up the Child component in the ChildWrapper component.

If I inspect the ChildWrapper VNode’s children property, it shows an object (with the default slot function) instead of an array with Child component which I expected. So my question is how do I get hold of the nested Child component?

Background of problem

Say the parent component is a Tab or a Carousel component and the child component is a TabItem or CarouselItem component. The reason I made a ChildWrapper component is to extend/override some of the props in the default Child component. The parent and child components are from a component library, and it has no knowledge of what ChildWrapper is, so the parent component can’t control how ChildWrapper should render.

2

Answers


  1. 2 approaches.

    1. Make a state (whichever state manager your using) like:
    exports defineState({
      visibleChildType: 'type'
    })
    

    In the Parent component, use whatever logic is required set the state value.

    Then, move the conditional render condition out of the Parent and into the child itself, while importing the state: like,

    // ChildComponent.vue
    <template>
      <div v-if="type === visibleChildType" />
    </template>
    
    <script>
    import visibleChildType from 'parentState'
    </script>
    
    1. Move the wrapper into the Child component itself.
    // ChildComponent.vue
    <template>
      <ChildWrapper v-if="needsWrapper">
        <div />
      </ChildWrapper>
    
      <div v-else=" />
    </template>
    

    This way, the child can wrap itself when necessary and your parents component doesn’t need to be refactored.

    Login or Signup to reply.
  2. I believe that obtaining specific, deeply nested child components directly from the parent is not a recommended approach. Child components may be nested deeply, beyond just one level.

    Using provide and inject allows you to achieve your goal seamlessly, in my opinion.

    I will provide a simple example to illustrate this.

    Parent component:

    
    import { provide, ref } from 'vue'
    
    const id = -1;
    const current = ref(0)
    
    function getId() {
      id++;
      return id;
    }
    
    provide('provide-key', {
      getId,
      current
    })
    

    Child component:

    
    import { inject,computed } from 'vue'
    
    const { getId, current } = inject('provide-key');
    const id = getId();
    const isShow = computed(() => id === current.value);
    

    The provided code is not complete, but I believe the main idea is conveyed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search