Appearance
useApiTable
The useApiTable composable provides a type-safe way to use the ApiTable component and its related components (Column, ApiTableLinkButton, ApiTableRemoveButton) with proper TypeScript type inference in Column slot props.
Problem
PrimeVue's DataTable component is engineered in such a way that the Column component doesn't know what type the data table is. This means when you use the Column component directly from PrimeVue, you don't get type inference in Column slot props (like the body slot), making it difficult to work with typed data in your templates.
For example, when using PrimeVue's Column directly:
vue
<template>
<ApiTable :list-state="listState">
<Column field="title" header="Title" />
<Column header="Actions">
<template #body="columnProps">
<!-- columnProps.data is typed as 'any' - no type inference! -->
{{ columnProps.data.title }}
</template>
</Column>
</ApiTable>
</template>
<script setup lang="ts">
import ApiTable from '@/components/Table/ApiTable.vue'
import Column from 'primevue/column'
import { usePostListState } from '@/models/Post/States'
const listState = usePostListState()
</script>Solution
The useApiTable composable solves this by providing a properly typed Column component that knows the data type used in the table. By importing components through the composable, the data type used in the table is also enforced on the table child components.
Function Signature
typescript
export default function useApiTable<
Api extends IApi<Model, ModelList>,
Model extends ModelType,
ModelList extends LaravelPaginationResponse<Model>,
>(_listState?: ListState<Api, Model, ModelList>): {
ApiTable: typeof ApiTable<Api, Model, ModelList>
ApiTableLinkButton: typeof ApiTableLinkButton
ApiTableRemoveButton: typeof ApiTableRemoveButton<Api, Model, ModelList>
Column: DefineComponent<
ColumnProps,
Omit<ColumnSlots, 'body'> & {
body(
scope: Omit<Parameters<ColumnSlots['body']>[0], 'data'> & { data: Plain<Model> },
): VNode[]
},
ColumnEmits
>
}Generic Parameters
Api
- Type:
IApi<Model, ModelList> - Details: The API instance type that extends the generic API interface. This type is automatically inferred from the
listStateparameter.
Model
- Type:
ModelType - Details: The model type representing the data item in the table. This type is automatically inferred from the
listStateparameter.
ModelList
- Type:
LaravelPaginationResponse<Model> - Details: The type representing the paginated response from the API. This type is automatically inferred from the
listStateparameter.
Parameters
listState (optional)
- Type:
ListState<Api, Model, ModelList> - Required:
false - Details: An optional
ListStateinstance. While not strictly required, passing thelistStatehelps TypeScript infer the generic types (Api,Model,ModelList) automatically, ensuring type safety throughout your component.
Return Value
The composable returns an object with the following properties:
ApiTable
- Type:
typeof ApiTable<Api, Model, ModelList> - Details: The typed
ApiTablecomponent with generic parameters applied.
ApiTableLinkButton
- Type:
typeof ApiTableLinkButton - Details: The
ApiTableLinkButtoncomponent for creating link buttons in table rows.
ApiTableRemoveButton
- Type:
typeof ApiTableRemoveButton<Api, Model, ModelList> - Details: The typed
ApiTableRemoveButtoncomponent with generic parameters applied.
Column
- Type:
DefineComponentwith typedbodyslot - Details: A properly typed
Columncomponent where thebodyslot receivesdataasPlain<Model>instead ofany. This provides full type inference and autocomplete in your templates.
Usage Example
vue
<template>
<ApiTable :list-state="listState">
<Column
field="title"
header="Title" />
<Column
field="body"
header="Body" />
<Column
:style="{ maxWidth: '112px', width: '112px' }"
header="">
<template #body="columnProps">
<!-- columnProps.data is now properly typed as Plain<Post>! -->
<ApiTableLinkButton
:to="{ name: 'posts-edit', params: { id: columnProps.data.id } }"
icon="fal fa-pen-to-square" />
<ApiTableRemoveButton :item="columnProps.data" />
</template>
</Column>
</ApiTable>
</template>
<script setup lang="ts">
import { onBeforeMount } from 'vue'
import useApiTable from '@/components/Table/useApiTable'
import Container from '@/components/Container.vue'
import { usePostListState } from '@/models/Post/States'
const listState = usePostListState()
const { ApiTable, Column, ApiTableLinkButton, ApiTableRemoveButton } = useApiTable(listState)
onBeforeMount(() => {
listState.getList()
})
</script>Benefits
Type Safety: The
Columncomponent'sbodyslot receives properly typed data, preventing runtime errors from accessing non-existent properties.Autocomplete: Your IDE will provide autocomplete suggestions for properties on
columnProps.data, making development faster and less error-prone.Type Checking: TypeScript will catch type errors at compile time, such as accessing properties that don't exist on the model.
Consistency: All table-related components (
ApiTable,Column,ApiTableLinkButton,ApiTableRemoveButton) share the same type information, ensuring consistency across your component.