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.7 KiB
5.7 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Avoid Prop Drilling - Use Provide/Inject for Deep Component Trees | MEDIUM | Passing props through many layers creates maintenance burden and tight coupling between intermediate components | best-practice |
|
Avoid Prop Drilling - Use Provide/Inject for Deep Component Trees
Impact: MEDIUM - Prop drilling occurs when you pass props through multiple component layers just to reach a deeply nested child. This creates tight coupling, makes refactoring difficult, and clutters intermediate components with props they don't use.
Vue's provide/inject API allows ancestor components to share data with any descendant, regardless of nesting depth.
Task Checklist
- Identify when props pass through 2+ intermediate components unchanged
- Use provide/inject for data needed by deeply nested descendants
- Use Pinia for global state shared across unrelated component trees
- Keep props for direct parent-child relationships
- Document provided values at the provider level
The Problem: Prop Drilling
<!-- App.vue -->
<template>
<MainLayout :user="user" :theme="theme" :locale="locale" />
</template>
<!-- MainLayout.vue - Doesn't use these props, just passes them -->
<template>
<Sidebar :user="user" :theme="theme" />
<Content :user="user" :locale="locale" />
</template>
<!-- Sidebar.vue - Still drilling... -->
<template>
<UserMenu :user="user" />
<ThemeToggle :theme="theme" />
</template>
<!-- UserMenu.vue - Finally uses user prop -->
<template>
<div>{{ user.name }}</div>
</template>
Problems:
MainLayoutandSidebarare cluttered with props they don't use- Adding a new shared value requires updating every component in the chain
- Removing a deeply nested component requires updating all ancestors
- Difficult to trace where data originates
Solution: Provide/Inject
Correct - Provider (ancestor):
<!-- App.vue -->
<script setup>
import { provide, ref, readonly } from 'vue'
const user = ref({ name: 'John', role: 'admin' })
const theme = ref('dark')
const locale = ref('en')
// Provide to all descendants
provide('user', readonly(user)) // readonly prevents mutations
provide('theme', theme)
provide('locale', locale)
// Provide update functions if needed
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
</script>
<template>
<MainLayout />
</template>
Correct - Intermediate components are now clean:
<!-- MainLayout.vue - No props needed -->
<template>
<Sidebar />
<Content />
</template>
<!-- Sidebar.vue - No props needed -->
<template>
<UserMenu />
<ThemeToggle />
</template>
Correct - Consumer (descendant):
<!-- UserMenu.vue -->
<script setup>
import { inject } from 'vue'
// Inject from any ancestor
const user = inject('user')
</script>
<template>
<div>{{ user.name }}</div>
</template>
<!-- ThemeToggle.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const updateTheme = inject('updateTheme')
function toggleTheme() {
updateTheme(theme.value === 'dark' ? 'light' : 'dark')
}
</script>
<template>
<button @click="toggleTheme">
Current: {{ theme }}
</button>
</template>
Best Practices for Provide/Inject
1. Use Symbol Keys for Large Apps
Avoid string key collisions with symbols:
// keys.js
export const UserKey = Symbol('user')
export const ThemeKey = Symbol('theme')
<script setup>
import { provide } from 'vue'
import { UserKey, ThemeKey } from './keys'
provide(UserKey, user)
provide(ThemeKey, theme)
</script>
<script setup>
import { inject } from 'vue'
import { UserKey } from './keys'
const user = inject(UserKey)
</script>
2. Provide Default Values
Handle cases where no ancestor provides the value:
<script setup>
import { inject } from 'vue'
// With default value
const theme = inject('theme', 'light')
// With factory function for objects (avoids shared reference)
const config = inject('config', () => ({ debug: false }), true)
</script>
3. Use Readonly for Data Safety
Prevent descendants from mutating provided data:
<script setup>
import { provide, ref, readonly } from 'vue'
const user = ref({ name: 'John' })
// Descendants can read but not mutate
provide('user', readonly(user))
// Provide separate method for updates
provide('updateUser', (updates) => {
Object.assign(user.value, updates)
})
</script>
4. Provide Computed Values for Reactivity
<script setup>
import { provide, computed } from 'vue'
const items = ref([1, 2, 3])
// Descendants will reactively update
provide('itemCount', computed(() => items.value.length))
</script>
When to Use What
| Scenario | Solution |
|---|---|
| Direct parent-child | Props |
| 1-2 levels deep | Props (drilling is acceptable) |
| Deep nesting, same component tree | Provide/Inject |
| Unrelated component trees | Pinia (state management) |
| Cross-app global state | Pinia |
| Plugin configuration | Provide/Inject from plugin install |
Provide/Inject vs Pinia
Provide/Inject:
- Scoped to component subtree
- Great for component library internals
- No DevTools support
- Ancestor-descendant relationships only
Pinia:
- Global, accessible anywhere
- Excellent DevTools integration
- Better for application state
- Works across unrelated components