#Issues with `Astro.slots.has` (regardless of default or named slots)

3 messages · Page 1 of 1 (latest)

worn pumice
#

This is a continuation of https://discord.com/channels/830184174198718474/1443695518927552574

Is there a way to only conditionally provide a default slot to a component?
Currently, we have to do this:

{section.data.title && section.data.subtitle && !section.data.description && (
  <TopicHeader
    subtitle={section.data.subtitle}
    title={section.data.title}
    level="h2"
  />
)}
{section.data.title && section.data.subtitle && section.data.description && (
  <TopicHeader
    subtitle={section.data.subtitle}
    title={section.data.title}
    level="h2"
  >
    {section.data.description}
  </TopicHeader>
)}

The following does not work, as astro already creates a default slot, even when it's empty:

{section.data.title && section.data.subtitle && (
  <TopicHeader
    subtitle={section.data.subtitle}
    title={section.data.title}
    level="h2"
  >{
    section.data.description && <Fragment slot="default">{section.data.description}</Fragment>
  }</TopicHeader>
)}

switching to a named slot doesn't seem to work either:

{section.data.title && section.data.subtitle && (
  <TopicHeader
    subtitle={section.data.subtitle}
    title={section.data.title}
    level="h2"
  >
    {section.data.description && <Fragment slot="description">{section.data.description}</Fragment>}
  </TopicHeader>
)}

with checking for it like so:

{Astro.slots.has('description') && (
  <p class="mt-6 max-w-lg text-lg sm:text-xl text-balance leading-relaxed empty:hidden">
    <slot name="description" />
  </p>
)}

Will still render an empty <p> tag.

And yes, i checked that the description is actually undefined. Rendering {JSON.stringify(Astro.slots)} also reveals description to be true.

flint socket
#

I don't recall exactly the details (there is a closed issue somewhere in the core repo where Nate explains it better I think) but this is a known limitation of Astro. Basically, at the time of evaluating if the slot should be rendered or not, Astro doesn't know if section.data.description is falsy or not, so the slot is always rendered. Something like that.

One way to work around this is:

{section.data.description && <Fragment slot={section.data.description ? "description" : ""}>{section.data.description}</Fragment>}

This seems a bit weird to duplicate the checking but this is unfortunately how it works... And, the same applies with "default" instead of "description".

Another way to work around this is, based on your first snippet:

  • always use {section.data.description}
  • in the component instead of using <slot />:

Something like:

---
const description = await Astro.slots.render("default");
---

{description && (<p set:html={description} />)}
{/* or */}
{description && (<div><Fragment set:html={description} /></div>)}
{/* or */}
{description ? (<p set:html={description} />) : null}

But when using Astro.slots.render(), you need to make sure what you're rendering is safe, because there is not escaping when using set:html, so if this is user input you'll need to sanitize it before doing that!

worn pumice