Skip to content

useDataTable

Store for schema-driven data tables with pagination, sorting, filtering, and selection.

Basic Usage

svelte
<script lang="ts">
import { useDataTable } from '@ybyra/svelte'
import { Scope } from '@ybyra/core'

const tableStore = useDataTable({
  schema: PersonSchema.provide(),
  scope: Scope.index,
  handlers: personHandlers,
  hooks: personHooks,
  component: componentContract,
  translate: t,
})

let table = $derived($tableStore)
</script>

{#if table.loading}
  <p>Loading...</p>
{:else if table.empty}
  <p>No records found</p>
{:else}
  <table>
    <thead>
      <tr>
        {#each table.columns as col (col.name)}
          <th onclick={() => tableStore.setSort(col.name)}>
            {t(`person.fields.${col.name}`)}
          </th>
        {/each}
      </tr>
    </thead>
    <tbody>
      {#each table.rows as row (tableStore.getIdentity(row))}
        <tr>
          {#each table.columns as col (col.name)}
            <td>{tableStore.formatValue(col.name, row[col.name], row)}</td>
          {/each}
        </tr>
      {/each}
    </tbody>
  </table>

  <div>
    <button disabled={table.page <= 1} onclick={() => tableStore.setPage(table.page - 1)}>Previous</button>
    <span>Page {table.page} of {table.totalPages}</span>
    <button disabled={table.page >= table.totalPages} onclick={() => tableStore.setPage(table.page + 1)}>Next</button>
  </div>
{/if}

Store API

useDataTable returns a Readable store extended with mutation methods. Use $tableStore to subscribe to the reactive snapshot and call methods on the store object itself.

typescript
const tableStore = useDataTable({ /* ... */ })

// reactive snapshot via auto-subscription
let table = $derived($tableStore)

// mutation methods on the store
tableStore.setPage(2)
tableStore.setSort('name')

Options

typescript
interface UseDataTableOptions {
  schema: SchemaProvide
  scope: ScopeValue
  handlers?: Record<string, HandlerFn>
  hooks?: SchemaHooks
  context?: Record<string, unknown>
  component: ComponentContract
  pageSize?: number
  translate?: TranslateContract
}
OptionRequiredDescription
schemaYesOutput of SchemaDefinition.provide()
scopeYesCurrent scope (typically Scope.index)
handlersNoAction handlers
hooksNoLifecycle hooks (fetch hooks provide data)
componentYesComponentContract implementation
pageSizeNoItems per page (default varies)
translateNoTranslation function

Snapshot Properties

Data

PropertyTypeDescription
rowsRecord<string, unknown>[]Current page data
loadingbooleanTrue while fetching data
emptybooleanTrue if no rows

Columns

PropertyTypeDescription
columnsResolvedColumn[]Visible columns
availableColumnsResolvedColumn[]All available columns
visibleColumnsstring[]Names of visible columns

Pagination

PropertyTypeDescription
pagenumberCurrent page
limitnumberItems per page
totalnumberTotal record count
totalPagesnumberTotal page count

Sorting

PropertyTypeDescription
sortFieldstring | undefinedCurrent sort field
sortOrder'asc' | 'desc' | undefinedSort direction

Filtering

PropertyTypeDescription
filtersRecord<string, unknown>Active filters

Selection

PropertyTypeDescription
selectedRecord<string, unknown>[]Selected rows

Actions

PropertyTypeDescription
actionsResolvedAction[]Top-level actions (e.g., "Add")

Store Methods

Methods are called on the store object, not the snapshot:

MethodDescription
setPage(page)Navigate to page
setLimit(limit)Change page size
setSort(field)Toggle sort on field
setFilter(field, value)Set a filter value
clearFilters()Clear all filters
toggleColumn(name)Toggle column visibility
isSelected(record)Check if row is selected
toggleSelect(record)Toggle row selection
selectAll()Select all rows
clearSelection()Clear selection
getRowActions(record)Get actions for a specific row
reload()Re-fetch current page data
formatValue(name, value, record)Format a cell value using column config
getIdentity(record)Get the identity key for a record

Column Configuration

Fields become table columns when marked with .column():

typescript
text().column()               // basic column
text().column().sortable()    // sortable
text().column().filterable()  // filterable

Fetch Hooks

Table data is loaded through fetch hooks:

typescript
const personHooks = PersonSchema.hooks({
  fetch: {
    [Scope.index]: async ({ params }) => {
      return personService.paginate(params)
    },
  },
})

The params object contains pagination, sorting, and filter state:

typescript
interface PaginateParams {
  page: number
  limit: number
  sort?: string
  order?: 'asc' | 'desc'
  filters?: Record<string, unknown>
}

The hook expects a PaginatedResult:

typescript
interface PaginatedResult<T> {
  data: T[]
  total: number
  page: number
  limit: number
}

ResolvedColumn

typescript
interface ResolvedColumn {
  name: string
  config: FieldConfig
  table: TableConfig
}

Released under the MIT License.