1. Good Practices
  2. Svelte Tables

Good Practices

Svelte Tables

The @tanstack/svelte-table package offers a powerful asynchronous state management for your API calls in Svelte.

We recommend using it in all of Table view components.

Usage Example

svelte
        <script lang="ts">
	import {
		createSvelteTable,
		flexRender,
		getCoreRowModel,
		getSortedRowModel,
		getExpandedRowModel,
		type ColumnDef,
		type TableOptions
	} from '@tanstack/svelte-table';
	import {
		Table,
		TableBody,
		TableBodyCell,
		TableBodyRow,
		TableHead,
		TableHeadCell,
		Card
	} from 'flowbite-svelte';
	import { writable } from 'svelte/store';
	import { FontAwesomeIcon } from 'fontawesome-svelte';
	import { goto } from '$app/navigation';
	import type { PostsInterface } from '$lib/models/posts';
	import { DateTime } from 'luxon';
	import SvelteMarkdown from 'svelte-markdown';

	export let posts: PostsInterface[] = [];

	const columns: ColumnDef<{ id: string }>[] = [
		{
			accessorKey: 'status',
			header: 'Status'
		},
		{
			accessorKey: 'title',
			header: 'Title'
		},
		{
			accessorKey: 'body',
			header: 'Description',
			cell: ({ getValue }) => {
				const val = getValue() as object;
				return val ? (val.length > 55 ? val.slice(0, 55) : val) : '';
			}
		},
		{
			accessorKey: 'publish_at',
			header: 'publish_at',
			cell: ({ getValue }) => {
				const val = getValue() as object[];
				const valueString = DateTime.fromISO(val).toLocaleString(DateTime.DATE_MED);
				return valueString;
			}
		},
		{
			accessorKey: 'author',
			header: 'Author'
		}
	];

	const options = writable<TableOptions<{ id: string }>>({
		data: posts,
		columns: columns,
		getCoreRowModel: getCoreRowModel(),
		getSortedRowModel: getSortedRowModel(),
		getExpandedRowModel: getExpandedRowModel(),
		manualSorting: true,
		manualPagination: true,
		onStateChange: () => {}
	});

	$: options.update((options) => ({
		...options,
		data: posts,
		state: {
			...options.state
		}
	}));

	const table = createSvelteTable(options);
</script>

<Card size="100" class="">
	<Table hoverable={true} striped={true}>
		<TableHead>
			{#each $table.getHeaderGroups() as headerGroup}
				{#each headerGroup.headers as header}
					<TableHeadCell>
						{#if !header.isPlaceholder}
							<a
								role="button"
								tabindex="0"
								href="#/"
								class:cursor-pointer={header.column.getCanSort()}
								class:select-none={header.column.getCanSort()}
								on:click|preventDefault={header.column.getToggleSortingHandler()}
							>
								<svelte:component
									this={flexRender(header.column.columnDef.header, header.getContext())}
								/>
								{#if header.column.getIsSorted().toString() === 'asc'}
									<FontAwesomeIcon
										icon={['fal', 'chevron-up']}
										size={'lg'}
										class="text-secondary"
									/>
								{:else if header.column.getIsSorted().toString() === 'desc'}
									<FontAwesomeIcon
										icon={['fal', 'chevron-down']}
										size={'lg'}
										class="text-secondary"
									/>
								{/if}
							</a>
						{/if}
					</TableHeadCell>
				{/each}
			{/each}
		</TableHead>
		<TableBody class="divide-y">
			{#each $table.getRowModel().rows as row, index}
				<TableBodyRow
					class="cursor-pointer"
					href=""
					on:click={() => {
						goto(`/posts/${posts[index].id}`);
					}}
				>
					{#each row.getVisibleCells() as cell}
						<TableBodyCell>
							{#if cell.id.includes('status')}
								{#if cell.row.original.status === 'published'}
									<span class="">
										<FontAwesomeIcon
											icon={['fas', 'badge-check']}
											size={'lg'}
											class="text-lime-600"
										/> Approved</span
									>
								{:else}
									<span class="">
										<FontAwesomeIcon
											icon={['fas', 'seal-question']}
											size={'lg'}
											class="text-amber-500"
										/> Pending</span
									>
								{/if}
							{:else if cell.id.includes('body')}
								<p class="line-clamp-2 w-52">
									<SvelteMarkdown source={cell.row.original?.body || '--'} />
								</p>
							{:else}
								<svelte:component
									this={flexRender(cell.column.columnDef.cell, cell.getContext())}
								/>
							{/if}
						</TableBodyCell>
					{/each}
				</TableBodyRow>
			{/each}
		</TableBody>
		<tfoot>
			{#each $table.getFooterGroups() as footerGroup}
				<tr>
					{#each footerGroup.headers as header}
						<th>
							{#if !header.isPlaceholder}
								<svelte:component
									this={flexRender(header.column.columnDef.footer, header.getContext())}
								/>
							{/if}
						</th>
					{/each}
				</tr>
			{/each}
		</tfoot>
	</Table>
</Card>