| name | vue-composables |
| description | Vue 3 composable patterns with reactive state, lifecycle hooks, and cleanup. Use when creating reusable logic, managing shared state, or implementing custom hooks. |
Vue Composables Skill
Patterns for creating reusable Vue 3 composables with proper TypeScript typing.
When to Use This Skill
- Creating reusable reactive logic
- Sharing state between components
- Implementing custom lifecycle hooks
- Managing async operations with cleanup
- Building utility composables
Reference Documentation
For detailed patterns and conventions, see:
Quick Reference
Basic Composable Structure
import { ref, computed, onMounted, onUnmounted } from 'vue'
export function useFeature(initialValue: string) {
const value = ref(initialValue)
const isLoading = ref(false)
const isEmpty = computed(() => value.value.length === 0)
function updateValue(newValue: string) {
value.value = newValue
}
async function fetchData() {
isLoading.value = true
try {
} finally {
isLoading.value = false
}
}
onMounted(() => {
fetchData()
})
return {
value,
isLoading,
isEmpty,
updateValue,
fetchData
}
}
Critical Rules
- Prefix with
use (e.g., useTaskFilter)
- Return reactive refs not raw values
- Clean up side effects in
onUnmounted
- Type all parameters and returns
- Keep composables focused on a single concern
With Cleanup
export function useEventListener(
target: EventTarget,
event: string,
handler: EventListener
) {
onMounted(() => {
target.addEventListener(event, handler)
})
onUnmounted(() => {
target.removeEventListener(event, handler)
})
}
With Async and Error Handling
export function useAsync<T>(asyncFn: () => Promise<T>) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const isLoading = ref(false)
async function execute() {
isLoading.value = true
error.value = null
try {
data.value = await asyncFn()
} catch (e) {
error.value = e instanceof Error ? e : new Error(String(e))
} finally {
isLoading.value = false
}
}
return { data, error, isLoading, execute }
}
Shared State Pattern
const globalCount = ref(0)
export function useGlobalCounter() {
function increment() {
globalCount.value++
}
return {
count: readonly(globalCount),
increment
}
}
File Naming
- Composables:
useFeatureName.ts (e.g., useTaskFilter.ts)
- Location:
frontend/src/composables/
- Tests:
useFeatureName.spec.ts alongside