Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
169 lines
4.0 KiB
Markdown
169 lines
4.0 KiB
Markdown
---
|
|
title: Pass Slots as Functions in Render Functions, Not Direct Children
|
|
impact: HIGH
|
|
impactDescription: Passing slot content incorrectly causes slots to not render or be treated as props
|
|
type: gotcha
|
|
tags: [vue3, render-function, slots, children, vnode]
|
|
---
|
|
|
|
# Pass Slots as Functions in Render Functions, Not Direct Children
|
|
|
|
**Impact: HIGH** - When creating component vnodes with `h()`, children must be passed as slot functions, not as direct children. Passing children directly may cause them to be interpreted as props or fail to render.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Pass slot content as functions: `{ default: () => [...] }`
|
|
- [ ] Use `null` for props when only passing slots to avoid misinterpretation
|
|
- [ ] For default slot only, a single function can be passed directly
|
|
- [ ] For named slots, use an object with slot function properties
|
|
|
|
**Incorrect:**
|
|
```js
|
|
import { h } from 'vue'
|
|
import MyComponent from './MyComponent.vue'
|
|
|
|
// WRONG: Children array may be misinterpreted
|
|
h(MyComponent, [
|
|
h('span', 'Slot content') // May not render as expected
|
|
])
|
|
|
|
// WRONG: Named slots as direct properties
|
|
h(MyComponent, {
|
|
header: h('h1', 'Title'), // This is a prop, not a slot!
|
|
default: h('p', 'Content') // This is also a prop
|
|
})
|
|
```
|
|
|
|
**Correct:**
|
|
```js
|
|
import { h } from 'vue'
|
|
import MyComponent from './MyComponent.vue'
|
|
|
|
// CORRECT: Default slot as function
|
|
h(MyComponent, null, {
|
|
default: () => h('span', 'Slot content')
|
|
})
|
|
|
|
// CORRECT: Single default slot shorthand
|
|
h(MyComponent, null, () => h('span', 'Slot content'))
|
|
|
|
// CORRECT: Named slots as functions
|
|
h(MyComponent, null, {
|
|
header: () => h('h1', 'Title'),
|
|
default: () => h('p', 'Content'),
|
|
footer: () => [
|
|
h('span', 'Footer item 1'),
|
|
h('span', 'Footer item 2')
|
|
]
|
|
})
|
|
|
|
// CORRECT: With props AND slots
|
|
h(MyComponent, { size: 'large' }, {
|
|
default: () => 'Button Text'
|
|
})
|
|
```
|
|
|
|
## Why Functions?
|
|
|
|
Slots in Vue 3 are functions for lazy evaluation:
|
|
|
|
```js
|
|
// Slots are called by the child component when needed
|
|
// This enables:
|
|
// 1. Scoped slots (passing data back)
|
|
// 2. Conditional rendering (slot not called if not used)
|
|
// 3. Proper reactivity tracking
|
|
|
|
h(MyList, { items }, {
|
|
// Scoped slot - receives data from child
|
|
item: ({ item, index }) => h('li', `${index}: ${item.name}`)
|
|
})
|
|
```
|
|
|
|
## The null Props Gotcha
|
|
|
|
When passing only slots, always use `null` for props:
|
|
|
|
```js
|
|
// WRONG: Slots object interpreted as props!
|
|
h(MyComponent, {
|
|
default: () => 'Hello'
|
|
})
|
|
// MyComponent receives: props.default = () => 'Hello'
|
|
|
|
// CORRECT: null signals "no props, next arg is slots"
|
|
h(MyComponent, null, {
|
|
default: () => 'Hello'
|
|
})
|
|
// MyComponent receives slot correctly
|
|
```
|
|
|
|
## Forwarding Slots from Parent
|
|
|
|
```js
|
|
export default {
|
|
setup(props, { slots }) {
|
|
return () => h(ChildComponent, null, {
|
|
// Forward all slots from parent
|
|
...slots,
|
|
|
|
// Or forward specific slots
|
|
default: slots.default,
|
|
header: slots.header
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
## Scoped Slots in Render Functions
|
|
|
|
```js
|
|
// Parent: Receives data from child via slot props
|
|
h(DataTable, { data: items }, {
|
|
row: (slotProps) => h('tr', [
|
|
h('td', slotProps.item.name),
|
|
h('td', slotProps.item.value)
|
|
])
|
|
})
|
|
|
|
// Child (DataTable): Calls slot with data
|
|
export default {
|
|
props: ['data'],
|
|
setup(props, { slots }) {
|
|
return () => h('table', [
|
|
h('tbody',
|
|
props.data.map(item =>
|
|
// Pass data to slot function
|
|
slots.row?.({ item })
|
|
)
|
|
)
|
|
])
|
|
}
|
|
}
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
```js
|
|
// Wrapper component forwarding slots
|
|
h('div', { class: 'wrapper' }, [
|
|
h(InnerComponent, null, slots)
|
|
])
|
|
|
|
// Conditional slot rendering
|
|
h('div', [
|
|
slots.header?.(), // Optional chaining - only render if slot provided
|
|
h('main', slots.default?.()),
|
|
slots.footer?.()
|
|
])
|
|
|
|
// Slot with fallback content
|
|
h('div', [
|
|
slots.default?.() ?? h('p', 'Default content when slot not provided')
|
|
])
|
|
```
|
|
|
|
## Reference
|
|
- [Vue.js Render Functions - Passing Slots](https://vuejs.org/guide/extras/render-function.html#passing-slots)
|
|
- [Vue.js Render Functions - Children](https://vuejs.org/guide/extras/render-function.html#children)
|