Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/ts-shallowref-for-dynamic-components.md
1---2title: Use shallowRef for Dynamic Component References3impact: MEDIUM4impactDescription: Storing components in reactive() or ref() triggers Vue warnings and can cause performance issues5type: gotcha6tags: [typescript, shallowRef, dynamic-components, reactivity, performance]7---89# Use shallowRef for Dynamic Component References1011**Impact: MEDIUM** - When storing Vue components in reactive state for dynamic rendering, using `ref()` or `reactive()` causes Vue warnings and unnecessary reactivity overhead. Use `shallowRef()` instead.1213## Task Checklist1415- [ ] Use `shallowRef` for storing component references16- [ ] Use `markRaw` when storing components in reactive objects17- [ ] Avoid wrapping component definitions with deep reactivity18- [ ] Check console for "[Vue warn]: Vue received a Component that was made a reactive object"1920## The Problem2122Vue components are objects with internal properties that should not be made reactive. When you store a component in `ref()` or `reactive()`, Vue traverses all properties deeply, which:23241. Triggers a console warning252. Creates unnecessary reactive proxies263. Can cause subtle bugs with component identity274. Impacts performance2829**Incorrect - Using ref() for components:**30```typescript31import { ref } from 'vue'32import ComponentA from './ComponentA.vue'33import ComponentB from './ComponentB.vue'3435// BAD: Vue will warn about making component reactive36const currentComponent = ref(ComponentA)3738function switchComponent() {39currentComponent.value = ComponentB40}41```4243**Console warning:**44```45[Vue warn]: Vue received a Component that was made a reactive object.46This can lead to unnecessary performance overhead and should be avoided47by marking the component with `markRaw` or using `shallowRef` instead of `ref`.48```4950## Solution 1: Use shallowRef5152`shallowRef` only makes the `.value` reference reactive, not the contents:5354```typescript55import { shallowRef, type Component } from 'vue'56import ComponentA from './ComponentA.vue'57import ComponentB from './ComponentB.vue'5859// CORRECT: shallowRef doesn't deep-proxy the component60const currentComponent = shallowRef<Component>(ComponentA)6162function switchComponent() {63currentComponent.value = ComponentB64}65```6667```vue68<template>69<component :is="currentComponent" />70</template>71```7273## Solution 2: Use markRaw in Reactive Objects7475When components are part of a larger reactive object:7677```typescript78import { reactive, markRaw, type Component } from 'vue'79import TabHome from './TabHome.vue'80import TabProfile from './TabProfile.vue'81import TabSettings from './TabSettings.vue'8283interface Tab {84name: string85component: Component86}8788// CORRECT: markRaw prevents reactivity on component objects89const tabs = reactive<Tab[]>([90{ name: 'Home', component: markRaw(TabHome) },91{ name: 'Profile', component: markRaw(TabProfile) },92{ name: 'Settings', component: markRaw(TabSettings) }93])9495const activeTab = shallowRef<Tab>(tabs[0])96```9798```vue99<template>100<div class="tabs">101<button102v-for="tab in tabs"103:key="tab.name"104@click="activeTab = tab"105>106{{ tab.name }}107</button>108</div>109<component :is="activeTab.component" />110</template>111```112113## TypeScript Typing114115For proper TypeScript support with dynamic components:116117```typescript118import { shallowRef, type Component, type DefineComponent } from 'vue'119120// Generic component type121const currentComponent = shallowRef<Component | null>(null)122123// Or more specific with props124interface MyComponentProps {125title: string126}127128const currentComponent = shallowRef<DefineComponent<MyComponentProps> | null>(null)129```130131## Dynamic Import with shallowRef132133When using dynamic imports for code splitting:134135```typescript136import { shallowRef, defineAsyncComponent, type Component } from 'vue'137138const currentComponent = shallowRef<Component | null>(null)139140async function loadComponent(name: string) {141const component = defineAsyncComponent(142() => import(`./components/${name}.vue`)143)144currentComponent.value = component145}146```147148## Component Registry Pattern149150For tab systems or wizard-like interfaces:151152```typescript153import { shallowRef, markRaw, type Component } from 'vue'154155// Type-safe component registry156const componentRegistry = {157home: markRaw(defineAsyncComponent(() => import('./Home.vue'))),158about: markRaw(defineAsyncComponent(() => import('./About.vue'))),159contact: markRaw(defineAsyncComponent(() => import('./Contact.vue')))160} as const161162type ComponentKey = keyof typeof componentRegistry163164const currentView = shallowRef<ComponentKey>('home')165166// Computed to get current component167const currentComponent = computed(() => componentRegistry[currentView.value])168```169170```vue171<template>172<component :is="currentComponent" />173</template>174```175176## When to Use Each Approach177178| Scenario | Solution |179|----------|----------|180| Single dynamic component reference | `shallowRef` |181| Component in reactive array/object | `markRaw` on component |182| Component map/registry | `markRaw` each component |183| Async components | `defineAsyncComponent` + `shallowRef` |184185## Common Mistakes186187### Mistake 1: Using computed with ref188189```typescript190// BAD: Still triggers warning191const components = ref([ComponentA, ComponentB])192const current = computed(() => components.value[index.value])193194// GOOD: Use shallowRef for the array195const components = shallowRef([ComponentA, ComponentB])196```197198### Mistake 2: Forgetting markRaw in map199200```typescript201// BAD: Components in map become reactive202const routes = reactive(new Map([203['home', HomeComponent],204['about', AboutComponent]205]))206207// GOOD: Mark each component as raw208const routes = reactive(new Map([209['home', markRaw(HomeComponent)],210['about', markRaw(AboutComponent)]211]))212```213214## Reference215216- [Vue.js Reactivity in Depth - Reducing Reactivity Overhead](https://vuejs.org/guide/extras/reactivity-in-depth.html#reducing-reactivity-overhead-for-large-immutable-structures)217- [Vue.js API - shallowRef](https://vuejs.org/api/reactivity-advanced.html#shallowref)218- [Vue.js API - markRaw](https://vuejs.org/api/reactivity-advanced.html#markraw)219