Skip to content

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)
  })
}

Student Information System Documentation