You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-csc-ui/doc/composables.md

9.1 KiB

Composables API Reference

Detailed usage examples for composables using <script setup>.

Table of Contents


useStore

Generic helpers for accessing Vuex store. It's a wrapper around Vuex that makes it easier to use the store without directly importing it everywhere.

Problem it solves: In Composition API, you can't use mapState, mapGetters, mapActions like in Options API. You need a different approach to access store state, getters, and actions reactively.

⚠️ Use useState, useGetters, and useActions helpers. Avoid useMutations if possible — wrap mutations in actions instead. Pinia has no mutations, so using useActions now makes future migration easier.

Functions

useStore()

Returns Vuex store instance. Use when: You need direct store access (rare).

<script setup>
import { computed } from 'vue'
import { useStore } from 'src/composables/useStore'

const store = useStore()
const user = computed(() => store.state.user.subscriber)
</script>

useState(moduleName, keys)

Map state properties from a module to reactive computed refs. Use when: You need reactive access to store state.

<script setup>
import { useState } from 'src/composables/useStore'

// Single property
const subscriber = useState('user', 'subscriber')

// Multiple properties
const { subscriber, loginState } = useState('user', [
  'subscriber',
  'loginState'
])
</script>

useGetters(moduleName, keys)

Map getters from a module to reactive computed refs. Use when: You need reactive access to store getters.

<script setup>
import { useGetters } from 'src/composables/useStore'

const { isLogged, isAdmin } = useGetters('user', [
  'isLogged',
  'isAdmin'
])
</script>

useActions(moduleName, keys)

Map actions from a module to functions. Use when: You need to dispatch store actions.

<script setup>
import { useActions } from 'src/composables/useStore'

const { login, logout } = useActions('user', ['login', 'logout'])

// Use the actions
await login({ username: 'test', password: 'pass' })
</script>

useMutations(moduleName, keys) Avoid unless it's absolutely necessary

Map mutations from a module to functions.

Remember, Pinia has no mutations. Create an action in your store that calls the mutation, then use useActions to access it.This helper remains in case the mutation needs to be accessed directly for some reason.

<script setup>
import { useMutations } from 'src/composables/useStore'

// Single mutation
const setUser = useMutations('user', 'setUser')
setUser({ id: 1, name: 'John' })

// Multiple mutations
const { setUser, clearUser } = useMutations('user', ['setUser', 'clearUser'])
</script>

Complete Example: Options API vs <script setup>

Options API:

<script>
import { mapState, mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState('user', ['subscriber']),
    ...mapGetters('user', ['isLogged', 'isAdmin'])
  },
  methods: {
    ...mapActions('user', ['login', 'logout']),

    async handleLogin() {
      await this.login({ username: 'test', password: 'pass' })
      if (this.isLogged) {
        console.log('Welcome', this.subscriber.username)
      }
    }
  }
}
</script>

<script setup>:

<script setup>
import { useState, useGetters, useActions } from 'src/composables/useStore'

// Map state
const { subscriber } = useState('user', ['subscriber'])

// Map getters
const { isLogged, isAdmin } = useGetters('user', ['isLogged', 'isAdmin'])

// Map actions
const { login, logout } = useActions('user', ['login', 'logout'])

// Use them
const handleLogin = async () => {
  await login({ username: 'test', password: 'pass' })
  if (isLogged.value) {
    console.log('Welcome', subscriber.value.username)
  }
}
</script>

<template>
  <div>
    <button @click="handleLogin">Login</button>
    <button @click="logout">Logout</button>
    <div v-if="isLogged">Welcome {{ subscriber.username }}</div>
  </div>
</template>

useGlobals

Access global application properties.

Available Functions

  • useAppConfig() - Application configuration
  • useConstants() - Global constants
  • useFilters() - Formatting functions
  • useValidationErrors() - Validation helpers

Example: useAppConfig()

<script setup>
import { useAppConfig } from 'src/composables/useGlobals'

const config = useAppConfig()

const apiEndpoint = `${config.baseHttpUrl}/api/users`
</script>

Example: useFilters()

<script setup>
import { useFilters } from 'src/composables/useGlobals'

const filters = useFilters()

const formatDate = (date) => filters.readableDate(date)
const formatPhone = (number) => filters.numberFormat(number)
</script>

<template>
  <div>
    <p>Date: {{ formatDate(new Date()) }}</p>
    <p>Phone: {{ formatPhone('1234567890') }}</p>
  </div>
</template>

Creating New Composables

When to Create a Composable

Create composables for:

  • Feature-specific logic that doesn't need global state (phonebook, NCOS, registrations)
  • Reusable business logic across components
  • Local state management with associated operations
  • API operations that don't affect global app state

Template for Feature Composables

Create composables that manage their own local state and business logic:

import { ref, computed } from 'vue'
import { getNcosLevels, getNcosSet, setPreference } from 'src/api/subscriber'
import { getSubscriberId } from 'src/auth'

/**
 * Composable for managing NCOS (Network Class of Service) levels and sets.
 * Manages local state and provides API operations.
 */
export function useNcos() {
  // Local reactive state
  const levels = ref([])
  const sets = ref([])
  const loading = ref(false)
  const error = ref(null)

  // Computed properties
  const hasLevels = computed(() => levels.value.length > 0)
  const hasSets = computed(() => sets.value.length > 0)

  // Business logic functions
  const loadLevels = async () => {
    loading.value = true
    error.value = null
    try {
      const list = await getNcosLevels()
      levels.value = list.items.map(ncos => ({
        label: ncos.level,
        value: ncos.id
      }))
    } catch (err) {
      error.value = err.message
      throw err
    } finally {
      loading.value = false
    }
  }

  const loadSets = async () => {
    loading.value = true
    error.value = null
    try {
      const list = await getNcosSet()
      sets.value = list.map(setNcos => ({
        label: setNcos.name,
        value: setNcos.id
      }))
    } catch (err) {
      error.value = err.message
      throw err
    } finally {
      loading.value = false
    }
  }

  const updateLevel = async (value) => {
    loading.value = true
    error.value = null
    try {
      await setPreference(getSubscriberId(), 'ncos', value)
    } catch (err) {
      error.value = err.message
      throw err
    } finally {
      loading.value = false
    }
  }

  const reset = () => {
    levels.value = []
    sets.value = []
    error.value = null
  }

  return {
    // State
    levels,
    sets,
    loading,
    error,

    // Computed
    hasLevels,
    hasSets,

    // Actions
    loadLevels,
    loadSets,
    updateLevel,
    reset
  }
}

Using the Feature Composable

<script setup>
import { onMounted } from 'vue'
import { useNcos } from 'src/composables/useNcos'

const {
  levels,
  sets,
  loading,
  error,
  hasLevels,
  loadLevels,
  loadSets,
  updateLevel
} = useNcos()

// Load data on mount
onMounted(async () => {
  await loadLevels()
  await loadSets()
})

const handleUpdateLevel = async (levelId) => {
  try {
    await updateLevel(levelId)
    // Show success message
  } catch (err) {
    // Error already set in composable
    console.error('Failed to update level:', error.value)
  }
}
</script>

<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <div v-else>
      <select
        @change="handleUpdateLevel($event.target.value)"
        :disabled="!hasLevels"
      >
        <option value="">Select NCOS Level</option>
        <option
          v-for="level in levels"
          :key="level.value"
          :value="level.value"
        >
          {{ level.label }}
        </option>
      </select>
    </div>
  </div>
</template>

Accessing Store Within a Composable

If your composable needs global store state, use useGetters or useActions:

import { ref } from 'vue'
import { useGetters } from 'src/composables/useStore'
import { getCustomerPhonebook } from 'src/api/subscriber'

export function useCustomerPhonebook() {
  // Access global state
  const { getCustomerId } = useGetters('user', ['getCustomerId'])

  // Local state
  const phonebook = ref([])
  const loading = ref(false)

  const loadPhonebook = async () => {
    loading.value = true
    try {
      const list = await getCustomerPhonebook({
        customerId: getCustomerId.value
      })
      phonebook.value = list.data
    } finally {
      loading.value = false
    }
  }

  return {
    phonebook,
    loading,
    loadPhonebook
  }
}