Composables
Reusable logic with Vue 3 composables.
Basic Pattern
typescript
// composables/useEntityList.ts
import { ref, computed } from 'vue'
import type { Entity, PaginatedResponse } from '@/api/types'
import { entityApi } from '@/api/services/domain.api'
export function useEntityList() {
const entities = ref<Entity[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const total = ref(0)
const hasEntities = computed(() => entities.value.length > 0)
async function fetchEntities(params?: Record<string, any>) {
loading.value = true
error.value = null
try {
const response = await entityApi.getAll(params)
entities.value = response.data.results
total.value = response.data.count
} catch (e) {
error.value = 'Failed to load entities'
throw e
} finally {
loading.value = false
}
}
return {
entities,
loading,
error,
total,
hasEntities,
fetchEntities
}
}Usage in Component
vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { useEntityList } from '@/composables/useEntityList'
const { entities, loading, fetchEntities } = useEntityList()
onMounted(() => fetchEntities())
</script>Common Composables
useToastMessages
typescript
import { useToast } from 'primevue/usetoast'
import { useI18n } from 'vue-i18n'
export function useToastMessages() {
const toast = useToast()
const { t } = useI18n()
function showSuccess(message: string) {
toast.add({
severity: 'success',
summary: t('common.success'),
detail: message,
life: 3000
})
}
function showError(message: string) {
toast.add({
severity: 'error',
summary: t('common.error'),
detail: message,
life: 5000
})
}
return { showSuccess, showError }
}useConfirmDialog
typescript
import { useConfirm } from 'primevue/useconfirm'
export function useConfirmDialog() {
const confirm = useConfirm()
function confirmDelete(onConfirm: () => void) {
confirm.require({
message: 'Are you sure you want to delete?',
header: 'Confirm Delete',
icon: 'pi pi-exclamation-triangle',
accept: onConfirm
})
}
return { confirmDelete }
}usePagination
typescript
import { ref, computed } from 'vue'
export function usePagination(pageSize = 20) {
const page = ref(1)
const total = ref(0)
const offset = computed(() => (page.value - 1) * pageSize)
const totalPages = computed(() => Math.ceil(total.value / pageSize))
function setPage(newPage: number) {
page.value = newPage
}
function reset() {
page.value = 1
}
return {
page,
total,
pageSize,
offset,
totalPages,
setPage,
reset
}
}Best Practices
Return Reactive References
Always return refs, not raw values:
typescript
// Good
return { count, increment }
// Bad - loses reactivity
return { count: count.value, increment }Clean Up Side Effects
typescript
import { onUnmounted } from 'vue'
export function useInterval(callback: () => void, ms: number) {
const id = setInterval(callback, ms)
onUnmounted(() => {
clearInterval(id)
})
}