---
title: Compose Composables for Complex Logic
impact: MEDIUM
impactDescription: Building composables from other composables creates reusable, testable building blocks
type: best-practice
tags: [vue3, composables, composition-api, patterns, code-organization]
---
# Compose Composables for Complex Logic
**Impact: MEDIUM** - Composables can (and should) call other composables. This composition pattern allows you to build complex functionality from smaller, focused, reusable pieces. Each composable handles one concern, and higher-level composables combine them.
This is one of the key advantages of the Composition API over mixins - dependencies are explicit and traceable.
## Task Checklist
- [ ] Extract reusable logic into focused, single-purpose composables
- [ ] Build complex composables by combining simpler ones
- [ ] Ensure each composable has a single responsibility
- [ ] Pass data between composed composables via parameters or refs
**Example: Building a Mouse Tracker from Smaller Composables**
```javascript
// composables/useEventListener.js - Low-level building block
import { onMounted, onUnmounted, toValue } from 'vue'
export function useEventListener(target, event, callback) {
onMounted(() => {
const el = toValue(target)
el.addEventListener(event, callback)
})
onUnmounted(() => {
const el = toValue(target)
el.removeEventListener(event, callback)
})
}
// composables/useMouse.js - Composes useEventListener
import { ref } from 'vue'
import { useEventListener } from './useEventListener'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// Reuse the event listener composable
useEventListener(window, 'mousemove', update)
return { x, y }
}
// composables/useMouseInElement.js - Composes useMouse
import { ref, computed } from 'vue'
import { useMouse } from './useMouse'
export function useMouseInElement(elementRef) {
const { x, y } = useMouse()
const elementX = computed(() => {
if (!elementRef.value) return 0
const rect = elementRef.value.getBoundingClientRect()
return x.value - rect.left
})
const elementY = computed(() => {
if (!elementRef.value) return 0
const rect = elementRef.value.getBoundingClientRect()
return y.value - rect.top
})
const isOutside = computed(() => {
if (!elementRef.value) return true
const rect = elementRef.value.getBoundingClientRect()
return x.value < rect.left || x.value > rect.right ||
y.value < rect.top || y.value > rect.bottom
})
return { x, y, elementX, elementY, isOutside }
}
```
## Pattern: Composable Dependency Chain
```javascript
// Layer 1: Primitives
export function useEventListener(target, event, callback) { /* ... */ }
export function useInterval(callback, delay) { /* ... */ }
export function useTimeout(callback, delay) { /* ... */ }
// Layer 2: Building on primitives
export function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
useEventListener(window, 'resize', () => {
width.value = window.innerWidth
height.value = window.innerHeight
})
return { width, height }
}
export function useOnline() {
const isOnline = ref(navigator.onLine)
useEventListener(window, 'online', () => isOnline.value = true)
useEventListener(window, 'offline', () => isOnline.value = false)
return { isOnline }
}
// Layer 3: Complex features combining multiple composables
export function useAutoSave(dataRef, saveFunction, options = {}) {
const { debounce = 1000, onlyWhenOnline = true } = options
const { isOnline } = useOnline()
const isSaving = ref(false)
const lastSaved = ref(null)
let timeoutId = null
watch(dataRef, (newData) => {
if (onlyWhenOnline && !isOnline.value) return
clearTimeout(timeoutId)
timeoutId = setTimeout(async () => {
isSaving.value = true
try {
await saveFunction(newData)
lastSaved.value = new Date()
} finally {
isSaving.value = false
}
}, debounce)
}, { deep: true })
return { isSaving, lastSaved, isOnline }
}
```
## Pattern: Code Organization with Composition
Extract inline composables when a component gets complex:
```vue
```
```vue
```
## Passing Data Between Composed Composables
```javascript
// Composables can accept refs from other composables
export function useFilteredProducts(products, filters) {
return computed(() => {
let result = toValue(products)
if (filters.value.category) {
result = result.filter(p => p.category === filters.value.category)
}
if (filters.value.minPrice > 0) {
result = result.filter(p => p.price >= filters.value.minPrice)
}
return result
})
}
export function useSortedProducts(products, sortConfig) {
return computed(() => {
const items = [...toValue(products)]
const { field, order } = sortConfig.value
return items.sort((a, b) => {
const comparison = a[field] > b[field] ? 1 : -1
return order === 'asc' ? comparison : -comparison
})
})
}
// Usage - composables are chained through their outputs
const { products, isLoading } = useFetch('/api/products')
const { filters } = useFilters()
const filteredProducts = useFilteredProducts(products, filters)
const { sortConfig } = useSortConfig()
const sortedProducts = useSortedProducts(filteredProducts, sortConfig)
```
## Advantages Over Mixins
| Composables | Mixins |
|-------------|--------|
| Explicit dependencies via imports | Implicit dependencies |
| Clear data flow via parameters | Unclear which mixin provides what |
| No namespace collisions | Properties can conflict |
| Easy to trace and debug | Hard to track origins |
| TypeScript-friendly | Poor TypeScript support |
## Reference
- [Vue.js Composables](https://vuejs.org/guide/reusability/composables.html)
- [Vue.js Composables vs Mixins](https://vuejs.org/guide/reusability/composables.html#comparisons-with-other-techniques)