--- 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 `` 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 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 ``, 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 `` or ``, 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)