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>
232 lines
5.6 KiB
Markdown
232 lines
5.6 KiB
Markdown
---
|
|
title: Use resolveComponent for String Component Names in Render Functions
|
|
impact: HIGH
|
|
impactDescription: String component names don't work in Vue 3 render functions; causes silent failures or runtime errors
|
|
type: gotcha
|
|
tags: [vue3, render-function, components, resolveComponent, migration]
|
|
---
|
|
|
|
# Use resolveComponent for String Component Names in Render Functions
|
|
|
|
**Impact: HIGH** - In Vue 2, render functions could use string names for globally or locally registered components. In Vue 3, you must either import components directly or use `resolveComponent()`. Using string names causes components to render as HTML elements or fail silently.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Import components directly when possible (preferred)
|
|
- [ ] Use `resolveComponent()` for dynamically registered components
|
|
- [ ] Use `resolveDynamicComponent()` for `<component :is="">` equivalent
|
|
- [ ] Call `resolveComponent()` inside `setup()` or the render function
|
|
- [ ] Handle the case when component is not found
|
|
|
|
**Incorrect:**
|
|
```js
|
|
import { h } from 'vue'
|
|
|
|
export default {
|
|
render() {
|
|
// WRONG: String names don't resolve to components
|
|
return h('div', [
|
|
h('my-component', { value: 1 }), // Renders <my-component> HTML element!
|
|
h('router-link', { to: '/' }, 'Home') // Also fails
|
|
])
|
|
}
|
|
}
|
|
```
|
|
|
|
**Correct (Direct Import - Preferred):**
|
|
```js
|
|
import { h } from 'vue'
|
|
import MyComponent from './MyComponent.vue'
|
|
import { RouterLink } from 'vue-router'
|
|
|
|
export default {
|
|
render() {
|
|
return h('div', [
|
|
h(MyComponent, { value: 1 }),
|
|
h(RouterLink, { to: '/' }, () => 'Home')
|
|
])
|
|
}
|
|
}
|
|
```
|
|
|
|
**Correct (resolveComponent for Registered Components):**
|
|
```js
|
|
import { h, resolveComponent } from 'vue'
|
|
|
|
export default {
|
|
components: {
|
|
MyComponent: () => import('./MyComponent.vue')
|
|
},
|
|
|
|
setup() {
|
|
// Resolve inside setup - component context is available
|
|
const MyComponent = resolveComponent('MyComponent')
|
|
|
|
return () => h('div', [
|
|
h(MyComponent, { value: 1 })
|
|
])
|
|
}
|
|
}
|
|
|
|
// Or resolve inside render function
|
|
export default {
|
|
render() {
|
|
const MyComponent = resolveComponent('MyComponent')
|
|
const RouterLink = resolveComponent('RouterLink')
|
|
|
|
return h('div', [
|
|
h(MyComponent, { value: 1 }),
|
|
h(RouterLink, { to: '/' }, () => 'Home')
|
|
])
|
|
}
|
|
}
|
|
```
|
|
|
|
## When to Use Each Approach
|
|
|
|
| Approach | Use When |
|
|
|----------|----------|
|
|
| Direct Import | Component is known at build time (most common) |
|
|
| `resolveComponent()` | Component is registered globally or locally by name |
|
|
| `resolveComponent()` | Dynamic component selection from registered set |
|
|
|
|
## Handling Missing Components
|
|
|
|
```js
|
|
import { h, resolveComponent } from 'vue'
|
|
|
|
export default {
|
|
setup() {
|
|
// resolveComponent returns the component or the string name if not found
|
|
const DynamicComponent = resolveComponent('MaybeRegistered')
|
|
|
|
// Check if resolution succeeded
|
|
if (typeof DynamicComponent === 'string') {
|
|
console.warn(`Component "${DynamicComponent}" not found`)
|
|
}
|
|
|
|
return () => h(DynamicComponent, { value: 1 })
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dynamic Component Selection
|
|
|
|
```js
|
|
import { h, resolveComponent, computed } from 'vue'
|
|
|
|
export default {
|
|
props: ['componentName'],
|
|
|
|
setup(props) {
|
|
// For truly dynamic components, resolve in render function
|
|
return () => {
|
|
const Component = resolveComponent(props.componentName)
|
|
return h(Component, { /* props */ })
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
For the equivalent of `<component :is="componentName">`, use `resolveDynamicComponent`:
|
|
|
|
```js
|
|
import { h, resolveDynamicComponent } from 'vue'
|
|
|
|
export default {
|
|
props: ['componentType'],
|
|
setup(props) {
|
|
return () => {
|
|
// Resolves string names, component objects, or built-in elements
|
|
const component = resolveDynamicComponent(props.componentType)
|
|
return h(component, { /* props */ })
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Practical Example: Tab Component
|
|
|
|
```js
|
|
import { h, resolveComponent, ref } from 'vue'
|
|
|
|
export default {
|
|
setup() {
|
|
const currentTab = ref('TabA')
|
|
const tabs = ['TabA', 'TabB', 'TabC']
|
|
|
|
return () => h('div', [
|
|
// Tab buttons
|
|
h('div', { class: 'tabs' },
|
|
tabs.map(tab =>
|
|
h('button', {
|
|
key: tab,
|
|
class: { active: currentTab.value === tab },
|
|
onClick: () => currentTab.value = tab
|
|
}, tab)
|
|
)
|
|
),
|
|
|
|
// Dynamic component based on current tab
|
|
h(resolveComponent(currentTab.value))
|
|
])
|
|
}
|
|
}
|
|
```
|
|
|
|
## Resolving Built-in Components
|
|
|
|
For built-in components like `<Transition>` or `<KeepAlive>`, import them directly from Vue:
|
|
|
|
```js
|
|
import { h, Transition, KeepAlive, Teleport, Suspense } from 'vue'
|
|
|
|
export default {
|
|
setup() {
|
|
return () => h(Transition, { name: 'fade' }, () =>
|
|
h('div', 'Content')
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Resolving Directives
|
|
|
|
Similar pattern for custom directives:
|
|
|
|
```js
|
|
import { h, resolveDirective, withDirectives } from 'vue'
|
|
|
|
export default {
|
|
render() {
|
|
const vFocus = resolveDirective('focus')
|
|
|
|
return withDirectives(
|
|
h('input', { type: 'text' }),
|
|
[[vFocus]]
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Migration from Vue 2
|
|
|
|
```js
|
|
// Vue 2 (worked with registered components)
|
|
render(h) {
|
|
return h('my-component', { props: { value: 1 } })
|
|
}
|
|
|
|
// Vue 3 (must resolve or import)
|
|
import { h, resolveComponent } from 'vue'
|
|
|
|
render() {
|
|
const MyComponent = resolveComponent('my-component')
|
|
return h(MyComponent, { value: 1 }) // Note: props go directly, not in 'props' key
|
|
}
|
|
```
|
|
|
|
## Reference
|
|
- [Vue 3 Migration - Render Function API](https://v3-migration.vuejs.org/breaking-changes/render-function-api.html)
|
|
- [Vue.js Render Function API - resolveComponent](https://vuejs.org/api/render-function.html#resolvecomponent)
|