Vue 3 Composables Design Patterns

Composables (composition functions) are one of the most central design patterns in Vue 3. I’ve been using them heavily in our workflow system for over half a year now and hit plenty of pitfalls along the way — here’s a writeup of what I learned.

What Are Composables

Simply put, Composables are functions that use the Vue Composition API to encapsulate stateful logic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// useWorkOrder.ts
import { ref, computed, onMounted } from 'vue'
import { fetchWorkOrders } from '@/api'

export function useWorkOrder() {
const orders = ref([])
const loading = ref(false)

const pendingCount = computed(() =>
orders.value.filter(o => o.status === 'pending').length
)

async function load() {
loading.value = true
try {
orders.value = await fetchWorkOrders()
} finally {
loading.value = false
}
}

onMounted(load)

return { orders, loading, pendingCount, reload: load }
}

A Few Practical Patterns

1. Self-Cleaning Listeners

Workflow systems often need polling, and you absolutely must clean up when leaving the page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export function usePolling(fn: () => Promise<void>, interval = 5000) {
const timer = ref<ReturnType<typeof setInterval>>()

function start() {
stop()
fn()
timer.value = setInterval(fn, interval)
}

function stop() {
if (timer.value) clearInterval(timer.value)
}

onUnmounted(stop)

return { start, stop }
}

2. Offline State Management

The biggest headache in a workflow system is the offline scenario:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export function useOnlineStatus() {
const isOnline = ref(navigator.onLine)

function handleOnline() { isOnline.value = true }
function handleOffline() { isOnline.value = false }

onMounted(() => {
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
})

onUnmounted(() => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
})

return { isOnline }
}

Pitfall Guide

  1. Don’t return reactive objects from a composable — just use ref and computed directly
  2. Lean on toRefs — it preserves reactivity when destructuring the return value of a composable
  3. Watch out for SSRonMounted only runs on the client; don’t touch the DOM during server-side rendering

Takeaway

A good Composable should satisfy three things:

  • Single responsibility — one function does one thing
  • Composable — assembled like building blocks
  • Testable — no dependency on a component instance

Once you internalize this pattern, the Vue 3 development experience levels up significantly.