#Nesting Astro components within Vue slots?

14 messages · Page 1 of 1 (latest)

crystal silo
#

As the title says: Should this be possible?

Playing with this it seems to work until I try to hydrate the Vue/React components - at which time there are hydration mismatch warnings and no slot content is rendered. e.g:

---
import BlockContent from '../components/BlockContent.astro';
import Accordion from '../vue/Accordion.vue';

const { items } = Astro.props;
---

{
  items.map((item) => (
    <Accordion title={item.title} client:visible>
      <BlockContent blocks={item.content} />
    </Accordion>
  ))
}
signal jolt
#

I don't know why there might be hydration warning but myy understanding is you can't just have a {} block at the top level

#

you should put that inside a <Fragment>

crystal silo
#

BlockContent is a Fragment essentially

#
---
import { toHTML } from '@portabletext/to-html';
import type { AstroComponentFactory } from 'astro/dist/runtime/server';

import AccordionSerializer from '../../serializers/Accordion.serializer.astro';
import BodyBlockSerializer from '../../serializers/BodyBlock.serializer.astro';
import SectionSerializer from '../../serializers/Section.serializer.astro';

const { blocks, serializers } = Astro.props;

const allSerializers: {
  types: Record<string, AstroComponentFactory | string>;
} = {
  types: {
    section: SectionSerializer,
    accordion: AccordionSerializer,
    bodyBlock: BodyBlockSerializer,
    ...(serializers?.types || {}),
  },
};

const stringSerializers = {
  types: {
    ...Object.entries(allSerializers.types).reduce((acc, [key, value]) => {
      if (typeof value === 'string') {
        return { ...acc, [key]: value };
      }
      return acc;
    }, {}),
  },
};
---

{
  blocks.map((block: { _type: string }) => {
    const Serializer = allSerializers.types[block._type];
    if (typeof Serializer === 'function') {
      return <Serializer {...block} />;
    }
    return (
      <Fragment
        set:html={toHTML(block, {
          components: stringSerializers,
        })}
      />
    );
  })
}
#

So, if I remove the client:visible from my Vue accordion component, all this works

#

It only breaks when I try hydrate the Vue component

crystal silo
#

I looks like nested components inside a Vue slot are getting messed up during rendering

#

"test content" should be in the spot where the random <astro-slot> is

<UolExample client:visible>
  <div>test content</div>
</UolExample>

Just a simple vue component alled <UolExample> with a <slot />

crystal silo
#

After some experimenting, the only client directive that works with Vue slots seems to be client:only

crystal silo
#

I've realised the problem is arising when using two Vue components with <slot />

#
// ComponentOne.vue
<template>
  <div>
    <slot />
  </div>
</template>

// ComponentTwo.vue
<template>
  <div>
    <slot />
  </div>
</template>
// Works
<ComponentOne client:visible>
  <p>test content</p>
</ComponentOne>

// Works
<ComponentTwo client:visible>
  <p>test content</p>
</ComponentTwo>

// Works (No Hydration)
<ComponentOne>
  <ComponentTwo>
    <p>test content</p>
  </ComponentTwo>
</ComponentOne>

// Does not work
<ComponentOne client:visible>
  <ComponentTwo>
    <p>test content</p>
  </ComponentTwo>
</ComponentOne>

// Does not work
<ComponentOne client:visible>
  <ComponentTwo client:visible>
    <p>test content</p>
  </ComponentTwo>
</ComponentOne>
crystal silo