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>
3.6 KiB
3.6 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Return Render Function from setup(), Not Direct VNodes | HIGH | Returning a vnode directly from setup makes it static; returning a function enables reactive updates | gotcha |
|
Return Render Function from setup(), Not Direct VNodes
Impact: HIGH - When using render functions with the Composition API, you must return a function that returns vnodes, not the vnodes directly. Returning vnodes directly creates a static render that never updates when reactive state changes.
Task Checklist
- Always return an arrow function from setup() when using render functions
- Never return h() calls directly from setup()
- Ensure reactive values are accessed inside the returned function
Incorrect:
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
// WRONG: Returns a static vnode, created once
// Clicking the button updates count.value, but the DOM never changes!
return h('div', [
h('p', `Count: ${count.value}`), // Captures count.value at setup time (0)
h('button', { onClick: increment }, 'Increment')
])
}
}
Correct:
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
// CORRECT: Returns a render function
// Vue calls this function on every reactive update
return () => h('div', [
h('p', `Count: ${count.value}`), // Re-evaluated each render
h('button', { onClick: increment }, 'Increment')
])
}
}
Why This Happens
// What Vue does internally:
// WRONG approach - setup runs once:
const result = setup()
// result is a vnode { type: 'div', children: [...] }
// Vue renders this once, then has no way to re-render
// CORRECT approach - setup returns a function:
const renderFn = setup()
// renderFn is () => h('div', ...)
// Vue calls renderFn() on mount
// Vue calls renderFn() again whenever dependencies change
Common Mistake: Mixing Template and Render Function
<script setup>
import { h, ref } from 'vue'
const count = ref(0)
// WRONG: Can't use render functions in script setup with templates
// This h() call does nothing
const node = h('div', count.value)
</script>
<template>
<!-- Template is used, render function is ignored -->
<div>{{ count }}</div>
</template>
If you need a render function with Composition API, don't use <script setup>:
<script>
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
</script>
<!-- No template - render function is used -->
Exposing Values While Using Render Functions
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const reset = () => { count.value = 0 }
// Expose methods for parent refs
expose({ reset })
// Still return the render function
return () => h('div', count.value)
}
}
With Slots
import { h, ref } from 'vue'
export default {
setup(props, { slots }) {
const count = ref(0)
return () => h('div', [
h('p', `Count: ${count.value}`),
// Slots must also be called inside the render function
slots.default?.()
])
}
}