In Vue 3, how can a block of code in a parent component template be conditionally rendered depending on whether or not a slotted child component has a prop passed to it?
Take the following code for example:
<Foo>
<Bar/>
<Bar baz="baz" />
</Foo>
Conditional slots can be used with $slots
in templates, i.e. v-if="$slots.default"
, but there doesn’t seem to be a way to conditionally render certain content in the parent based on whether or not a slot has a prop.
Inside of <Foo/>
, note the v-if
does not work but this is the idea.
<script setup></script>
<template>
<h1>Foo</h1>
<template v-if="$slots.default.props.baz">
<!-- add custom logic here depending on whether or not "baz" prop is present -->
</template>
<slot/>
</template>
This also will not work:
<script setup></script>
<template>
<h1>Foo</h1>
<template v-for="slot in $slots.default">
<div v-if="slot.props.baz"><!-- do something --></div>
</template>
<slot/>
</template>
The only working solution is to use the useSlots()
composable and use a render function.
<script setup>
const slots = useSlots()
slots.default().forEach(slot => {
if (slots.props?.baz)
console.log('baz prop present')
else
console.log('baz prop not present')
})
</script>
The thing is that vue templates are compiled to render functions, so it would seem that there is a way to access props on $slots
.
2
Answers
Just wrap your slot logic into a functional component (a render function):
Playground
This is possible with a template. The problem in the original code is that
$slots.default
is a function which cannot be iterated.It should be:
:is="vnode"
is a working but undocumented use and can be extended to:is="() => vnode"
in case of problems.Render function is more flexible than a template, and it’s more efficient to do this there, as suggested in another answer. And it’s even more efficient to do this in
script
rather thanscript setup
to not work around its limitations: