We use cookies to enhance your experience on the site
CodeWorlds

Composables in Production

The final stage before deploying NOVA LAB systems on Mars - let's learn proven patterns used in production Vue 3 applications.

File Organization for Composables

In large projects, a clear file structure is crucial.

1src/
2  composables/
3    useAuth.ts         # Authorization
4    useNotifications.ts # Notifications
5    useSensors.ts      # Sensor data
6    useTheme.ts        # App theme
7    index.ts           # Re-export all composables
1// composables/index.ts - central export point
2export { useAuth } from './useAuth'
3export { useNotifications } from './useNotifications'
4export { useSensors } from './useSensors'
5export { useTheme } from './useTheme'
6
7// Import in component - clean and readable
8import { useAuth, useNotifications } from '@/composables'

Typing Composables with TypeScript

TypeScript ensures type safety and better IDE suggestions.

1import { ref, computed, Ref, ComputedRef } from 'vue'
2
3// Interface for returned API
4interface UsePaginationReturn {
5  currentPage: Ref<number>
6  totalPages: ComputedRef<number>
7  paginatedItems: ComputedRef<any[]>
8  goToPage: (page: number) => void
9  nextPage: () => void
10  prevPage: () => void
11}
12
13// Typed composable
14function usePagination<T>(
15  items: Ref<T[]>,
16  perPage: number = 10
17): UsePaginationReturn {
18  const currentPage = ref(1)
19
20  const totalPages = computed(() =>
21    Math.ceil(items.value.length / perPage)
22  )
23
24  const paginatedItems = computed(() => {
25    const start = (currentPage.value - 1) * perPage
26    return items.value.slice(start, start + perPage)
27  })
28
29  function goToPage(page: number) {
30    if (page >= 1 && page <= totalPages.value) {
31      currentPage.value = page
32    }
33  }
34
35  function nextPage() { goToPage(currentPage.value + 1) }
36  function prevPage() { goToPage(currentPage.value - 1) }
37
38  return {
39    currentPage,
40    totalPages,
41    paginatedItems,
42    goToPage,
43    nextPage,
44    prevPage
45  }
46}

Composable with Configuration Options

The options pattern allows flexible composable configuration - like configuration panels for NOVA LAB modules.

1import { ref, watch, onUnmounted } from 'vue'
2
3interface UsePollingOptions {
4  interval?: number
5  immediate?: boolean
6  retryOnError?: boolean
7  maxRetries?: number
8  onError?: (error: Error) => void
9}
10
11function usePolling(
12  fetchFn: () => Promise<any>,
13  options: UsePollingOptions = {}
14) {
15  const {
16    interval = 5000,
17    immediate = true,
18    retryOnError = true,
19    maxRetries = 3,
20    onError
21  } = options
22
23  const data = ref(null)
24  const error = ref(null)
25  const isPolling = ref(false)
26  const retryCount = ref(0)
27  let timerId: number | null = null
28
29  async function poll() {
30    try {
31      data.value = await fetchFn()
32      error.value = null
33      retryCount.value = 0
34    } catch (e) {
35      error.value = e
36      onError?.(e)
37
38      if (!retryOnError || retryCount.value >= maxRetries) {
39        stopPolling()
40        return
41      }
42      retryCount.value++
43    }
44  }
45
46  function startPolling() {
47    if (isPolling.value) return
48    isPolling.value = true
49    if (immediate) poll()
50    timerId = setInterval(poll, interval)
51  }
52
53  function stopPolling() {
54    isPolling.value = false
55    if (timerId) {
56      clearInterval(timerId)
57      timerId = null
58    }
59  }
60
61  onUnmounted(stopPolling)
62
63  return { data, error, isPolling, retryCount, startPolling, stopPolling }
64}
65
66// Usage with options
67const sensorData = usePolling(
68  () => fetch('/api/sensors').then(r => r.json()),
69  {
70    interval: 3000,
71    immediate: true,
72    maxRetries: 5,
73    onError: (e) => console.error('Polling failed:', e)
74  }
75)

Composable with provide/inject for Large Applications

In production applications we often need shared state without prop drilling.

1import { ref, computed, provide, inject, readonly } from 'vue'
2
3const StoreKey = Symbol('AppStore')
4
5// Created once in the root component (App.vue)
6function createAppStore() {
7  const user = ref(null)
8  const isAuthenticated = computed(() => user.value !== null)
9  const notifications = ref([])
10
11  function login(userData) { user.value = userData }
12  function logout() { user.value = null }
13  function notify(message, type = 'info') {
14    notifications.value.push({
15      id: Date.now(),
16      message,
17      type,
18      timestamp: new Date()
19    })
20  }
21
22  const store = {
23    user: readonly(user),
24    isAuthenticated,
25    notifications: readonly(notifications),
26    login,
27    logout,
28    notify
29  }
30
31  provide(StoreKey, store)
32  return store
33}
34
35// Used in any nested component
36function useAppStore() {
37  const store = inject(StoreKey)
38  if (!store) {
39    throw new Error('useAppStore requires createAppStore in App.vue')
40  }
41  return store
42}

Module Summary

In this module you learned advanced techniques for working with composables:

  • Factory composables - creating multiple composable instances with different configurations
  • Composition - combining composables into larger systems, composable using another composable
  • Dependency Injection -
    provide/inject
    for shared state without prop drilling
  • Reactive patterns - Singleton, Event Bus, State Machine, Observer - all with Vue reactivity
  • VueUse - 200+ ready-made composables (
    useLocalStorage
    ,
    useDark
    ,
    useWindowSize
    )
  • Testing - unit testing composables with Vitest, mocking, testing async operations
  • Optimization -
    shallowRef
    for large data, computed caching, avoiding memory leaks
  • Production - TypeScript typing, file organization, configuration options pattern

These techniques will allow you to build scalable and performant Vue 3 applications - ready for space missions!

Go to CodeWorlds