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>
5.9 KiB
5.9 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Use shallowRef for Dynamic Component References | MEDIUM | Storing components in reactive() or ref() triggers Vue warnings and can cause performance issues | gotcha |
|
Use shallowRef for Dynamic Component References
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.
Task Checklist
- Use
shallowReffor storing component references - Use
markRawwhen storing components in reactive objects - Avoid wrapping component definitions with deep reactivity
- Check console for "[Vue warn]: Vue received a Component that was made a reactive object"
The Problem
Vue 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:
- Triggers a console warning
- Creates unnecessary reactive proxies
- Can cause subtle bugs with component identity
- Impacts performance
Incorrect - Using ref() for components:
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
// BAD: Vue will warn about making component reactive
const currentComponent = ref(ComponentA)
function switchComponent() {
currentComponent.value = ComponentB
}
Console warning:
[Vue warn]: Vue received a Component that was made a reactive object.
This can lead to unnecessary performance overhead and should be avoided
by marking the component with `markRaw` or using `shallowRef` instead of `ref`.
Solution 1: Use shallowRef
shallowRef only makes the .value reference reactive, not the contents:
import { shallowRef, type Component } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
// CORRECT: shallowRef doesn't deep-proxy the component
const currentComponent = shallowRef<Component>(ComponentA)
function switchComponent() {
currentComponent.value = ComponentB
}
<template>
<component :is="currentComponent" />
</template>
Solution 2: Use markRaw in Reactive Objects
When components are part of a larger reactive object:
import { reactive, markRaw, type Component } from 'vue'
import TabHome from './TabHome.vue'
import TabProfile from './TabProfile.vue'
import TabSettings from './TabSettings.vue'
interface Tab {
name: string
component: Component
}
// CORRECT: markRaw prevents reactivity on component objects
const tabs = reactive<Tab[]>([
{ name: 'Home', component: markRaw(TabHome) },
{ name: 'Profile', component: markRaw(TabProfile) },
{ name: 'Settings', component: markRaw(TabSettings) }
])
const activeTab = shallowRef<Tab>(tabs[0])
<template>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.name"
@click="activeTab = tab"
>
{{ tab.name }}
</button>
</div>
<component :is="activeTab.component" />
</template>
TypeScript Typing
For proper TypeScript support with dynamic components:
import { shallowRef, type Component, type DefineComponent } from 'vue'
// Generic component type
const currentComponent = shallowRef<Component | null>(null)
// Or more specific with props
interface MyComponentProps {
title: string
}
const currentComponent = shallowRef<DefineComponent<MyComponentProps> | null>(null)
Dynamic Import with shallowRef
When using dynamic imports for code splitting:
import { shallowRef, defineAsyncComponent, type Component } from 'vue'
const currentComponent = shallowRef<Component | null>(null)
async function loadComponent(name: string) {
const component = defineAsyncComponent(
() => import(`./components/${name}.vue`)
)
currentComponent.value = component
}
Component Registry Pattern
For tab systems or wizard-like interfaces:
import { shallowRef, markRaw, type Component } from 'vue'
// Type-safe component registry
const componentRegistry = {
home: markRaw(defineAsyncComponent(() => import('./Home.vue'))),
about: markRaw(defineAsyncComponent(() => import('./About.vue'))),
contact: markRaw(defineAsyncComponent(() => import('./Contact.vue')))
} as const
type ComponentKey = keyof typeof componentRegistry
const currentView = shallowRef<ComponentKey>('home')
// Computed to get current component
const currentComponent = computed(() => componentRegistry[currentView.value])
<template>
<component :is="currentComponent" />
</template>
When to Use Each Approach
| Scenario | Solution |
|---|---|
| Single dynamic component reference | shallowRef |
| Component in reactive array/object | markRaw on component |
| Component map/registry | markRaw each component |
| Async components | defineAsyncComponent + shallowRef |
Common Mistakes
Mistake 1: Using computed with ref
// BAD: Still triggers warning
const components = ref([ComponentA, ComponentB])
const current = computed(() => components.value[index.value])
// GOOD: Use shallowRef for the array
const components = shallowRef([ComponentA, ComponentB])
Mistake 2: Forgetting markRaw in map
// BAD: Components in map become reactive
const routes = reactive(new Map([
['home', HomeComponent],
['about', AboutComponent]
]))
// GOOD: Mark each component as raw
const routes = reactive(new Map([
['home', markRaw(HomeComponent)],
['about', markRaw(AboutComponent)]
]))