Skip to content

Composables

Composables are reusable composition functions that encapsulate and reuse stateful logic.

IONFLOW uses Vue 3 Composition API composables (similar to React hooks) for:

  • Shared reactive state
  • Reusable business logic
  • Data fetching patterns
  • UI behaviors
ComposableLocationDescription
useDataTableParamssrc/hooks/data.table.params.tsDataTable pagination and filtering
useThemeColorsrc/hooks/useThemeColor.tsTheme color management

Manages DataTable state including pagination, sorting, and filtering.

import { useDataTableParams } from '@/hooks/data.table.params';
const {
first,
rows,
sortField,
sortOrder,
filters,
onPage,
onSort,
onFilter,
queryParams,
} = useDataTableParams();
PropertyTypeDescription
firstRef<number>First row index
rowsRef<number>Rows per page
sortFieldRef<string>Sort field name
sortOrderRef<number>Sort direction (1/-1)
filtersRef<object>Filter values
onPage(event) => voidPage change handler
onSort(event) => voidSort change handler
onFilter(event) => voidFilter change handler
queryParamsComputedRefAPI query parameters
<template>
<DataTable
:value="data"
:first="first"
:rows="rows"
:sortField="sortField"
:sortOrder="sortOrder"
:filters="filters"
@page="onPage"
@sort="onSort"
@filter="onFilter"
paginator
lazy
>
<!-- columns -->
</DataTable>
</template>
<script setup lang="ts">
import { useDataTableParams } from '@/hooks/data.table.params';
import { useQuery } from '@tanstack/vue-query';
const {
first,
rows,
sortField,
sortOrder,
filters,
onPage,
onSort,
onFilter,
queryParams,
} = useDataTableParams();
const { data } = useQuery({
queryKey: ['items', queryParams],
queryFn: () => fetchItems(queryParams.value),
});
</script>

Manages application theme (light/dark mode).

import { useThemeColor } from '@/hooks/useThemeColor';
const { isDark, toggle, setTheme } = useThemeColor();
PropertyTypeDescription
isDarkRef<boolean>Dark mode state
toggle() => voidToggle theme
setTheme(dark: boolean) => voidSet specific theme

Create a composable when:

  • Logic is used in 2+ components
  • State needs to be shared across components
  • Complex reactive logic needs encapsulation
  • Prefix with use
  • Use camelCase
  • Be descriptive
// ✅ Good names
useFlowBuilder
useConnectionStatus
useFormValidation
// ❌ Bad names
flowBuilder
handleConnection
validate
import { ref, computed, onMounted, onUnmounted } from 'vue';
type UseExampleOptions = {
immediate?: boolean;
};
export function useExample(options: UseExampleOptions = {}) {
// State
const data = ref<Data | null>(null);
const loading = ref(false);
const error = ref<Error | null>(null);
// Computed
const hasData = computed(() => data.value !== null);
// Methods
const fetch = async () => {
loading.value = true;
try {
data.value = await fetchData();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
};
const reset = () => {
data.value = null;
error.value = null;
};
// Lifecycle
onMounted(() => {
if (options.immediate) {
fetch();
}
});
// Return public API
return {
data,
loading,
error,
hasData,
fetch,
reset,
};
}

Place composables in src/hooks/:

src/hooks/
├── data.table.params.ts
├── useThemeColor.ts
└── [composable].ts
  • Return reactive refs
  • Clean up side effects in onUnmounted
  • Type all parameters and returns
  • Handle errors gracefully
  • Document public API
  • Don’t use any types
  • Don’t mutate props
  • Don’t forget cleanup
  • Don’t make too many responsibilities