Search code examples
javascriptvue.jsvuejs3vue-componentvue-render-function

How to get specific nested child component from parent component slot in render function?


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.


Solution

  • 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.