A headless table hook for Qwik, inspired by @tanstack/table.
pnpm add @oqx/qwik-table
npm install @oqx/qwik-table
yarn add @oqx/qwik-table
useTable
generates a data representation of a table that can be iterated through to create UI.
export default component$<{ users: Signal<User[]> }>(({ users }) => {
const table = useTable({ data: users, getColumnDefs$ });
return (
<div>
<table>
<thead>
<tr>
{table.headerGroups.value?.map((header) => (
<td key={header.id}>{header.cell}</td>
))}
</tr>
</thead>
<tbody>
{table.rowGroups.value?.map((row, i) => (
<tr key={i + "row"}>
{row.map((cell) => (
<td key={cell.id}>{cell.value}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
});
A fallback value for when a piece of data is empty. Defaults to --
.
getColumnDefs$
is a QRL
that returns an array of ColumnDef, or column definitons. QRL
is necessary since ColumnDef can contain values that cannot be serialized.
ColumnDef
is a building block that provides instructions on how to derive rows and columns from the data
parameter. Below is a table of ColumnDef
properties, their corresponding types, and descriptions of each.
ID is a string
used as a key and a mechanism for sorting. It's also used to derive keys for JSX iterables behind the scenes.
Type
string;
cell
provides an API for adding HTML/JSX to a column. The info
argument represents the value
of the property selected from data
via accessorKey
or accessorFn
.
Type
(info: { value: string | number | undefined }) =>
JSXNode | Element | string | number;
The table header is very similar to cell
, except it received parameters that provide information about the sort order, and whether that table header's column is the active sort column.
Type
string | (props: {
isSortedBy: boolean;
sortOrder?: "asc" | "desc";
id?: string;
}) => JSXNode | Serializable | Element
An accessor function for when you have a more complex value, like an object, to derive data from. For example, a nested object:
const data = [
{
// ...
details: {
address: {
city: "Minneapolis",
},
},
},
];
// state will be equal to data
const getColumnDefs$ = $(() => [
{
id: "city",
header: "City",
accessorFn(state) {
return state.details.address.city;
},
},
// ...
]);
Type
(data: TData) => string | number | undefined`
data
represents a Signal
that is an array of objects. It is to be transformed into table headers, rows, and columns via ColumnDef
definitions provided by getColumnDefs$
.
For this example, a table of users will be created, where a user looks like:
type User = {
name: string;
email: string;
age: string;
};
With User
defined, a getColumnDefs$
QRL would look like this:
const getColumnDefs$ = $((): ColumnDef<User>[] => [
{
accessorKey: "name",
id: "name",
header: "Name",
},
{
accessorKey: "email",
id: "email",
header: "Email",
},
{
accessorKey: "age",
id: "age",
header: "Age",
},
]);
Adding ColumnDef
with a generic to the return type will allow validation of accessorKey
.
flexRender
will determine whether the value passed in is a string or a function and return a value that can be rendered in the UI.
For example, in ColumnDef
, instead of adding a string
to the header
property, it can instead be a function that returns a JSXNode or element. This removes the ternary statement boilerplate you'd otherwise need.
const header = (value: string) => () => <span>{value} π</span>;
const getColumnDefs$ = $((): ColumnDef<User>[] => [
{
accessorKey: "name",
id: "name",
header: header("Name"),
},
// ...
]);
Sorting can be achieved by adding data-usetable-sort
to an element -- particularly an element assigned to ColumnDef.header
.
Here's an example:
const header =
(value: string) =>
({
isSortedBy,
sortOrder,
id,
}: {
isSortedBy: boolean;
sortOrder?: "asc" | "desc";
id?: string;
}) => (
// Adding data-usetable-sort to an element will automatically
// add onClick sort functionality.
<button data-usetable-sort={id}>
{value}{" "}
<span
class={{
chevron: true,
"chevron--down": isSortedBy && sortOrder === "desc",
"chevron--up": isSortedBy && sortOrder === "asc",
}}
/>
</button>
);
A JSXNode assigned to ColumnDef.header
will receive the following props:
type HeaderArgs = {
// true if the column is currently the one the table is sorted by
isSortedBy: boolean;
// current sort order
sortOrder?: "asc" | "desc";
// id of the column being sorted
id?: string;
};
Inspired by @tanstack/table.