Filters (Server-side)
This page features a server-side filtering, pagination, and sorting demo with a simulated backend API. State is managed in the component and sent to the server on each change. This is the standard approach for backend integrations where the server handles filtering, pagination, and sorting logic. See the Filters Guide for implementation details.
Username | Role | Account Status | Account Created On | Last Visited At | Visit Count |
|---|
{
"columnFilters": [],
"globalFilter": "",
"pagination": {
"pageIndex": 0,
"pageSize": 10
},
"sorting": []
}
import {useMemo, useState} from "react"
import {useQuery} from "@tanstack/react-query"
import {Search} from "lucide-react"
import {
type ColumnDef,
type ColumnFiltersState,
getCoreRowModel,
type PaginationState,
type SortingState,
} from "@qualcomm-ui/core/table"
import {Pagination} from "@qualcomm-ui/react/pagination"
import {Popover} from "@qualcomm-ui/react/popover"
import {ProgressRing} from "@qualcomm-ui/react/progress-ring"
import {
flexRender,
Table,
useReactTable,
useTablePagination,
} from "@qualcomm-ui/react/table"
import {TextInput} from "@qualcomm-ui/react/text-input"
import {useDebounce} from "@qualcomm-ui/react-core/effects"
import {CodeHighlight} from "@qualcomm-ui/react-mdx/code-highlight"
import {TableColumnFilter} from "./filters"
import {fetchData, type User, type UserColumnMeta} from "./use-data"
export function FiltersServerSideDemo() {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
// always memoize your data and columns
const userColumns: ColumnDef<User, any, UserColumnMeta>[] = useMemo(
() => [
{
accessorKey: "username",
header: "Username",
id: "username",
meta: {
filterLabel: "Search by username",
},
},
{
accessorKey: "role",
header: "Role",
id: "role",
meta: {
filterLabel: "Filter by role",
},
size: 120,
},
{
accessorKey: "accountStatus",
header: "Account Status",
id: "accountStatus",
meta: {
filterLabel: "Filter by account status",
},
minSize: 170,
},
{
accessorKey: "createdAt",
enableColumnFilter: false,
header: "Account Created On",
id: "createdAt",
minSize: 205,
},
{
accessorKey: "lastVisitedAt",
enableColumnFilter: false,
header: "Last Visited At",
id: "lastVisitedAt",
minSize: 205,
},
{
accessorKey: "visitCount",
header: "Visit Count",
id: "visitCount",
meta: {
filterLabel: "Filter by visit count",
},
},
],
[],
)
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [globalFilter, setGlobalFilter] = useState("")
const [sorting, setSorting] = useState<SortingState>([])
const debouncedColumnFilters = useDebounce(columnFilters, 300)
const debouncedGlobalFilter = useDebounce(globalFilter, 300)
const {data, fetchStatus, isFetching} = useQuery({
placeholderData: (previousData) => previousData,
queryFn: () =>
fetchData({
columnFilters: debouncedColumnFilters,
globalFilter: debouncedGlobalFilter,
pageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
sorting,
}),
queryKey: [
"data",
pagination,
debouncedColumnFilters,
debouncedGlobalFilter,
sorting,
],
})
const table = useReactTable({
columns: userColumns,
data: useMemo(() => data?.users ?? [], [data?.users]),
getCoreRowModel: getCoreRowModel(),
manualFiltering: true,
manualPagination: true,
manualSorting: true,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
onPaginationChange: setPagination,
onSortingChange: setSorting,
pageCount: data?.pageCount,
state: {
columnFilters,
globalFilter,
pagination,
sorting,
},
})
const paginationProps = useTablePagination(table, {
totalCount: data?.totalUsers,
})
return (
<div className="flex w-full flex-col gap-4 p-2">
<Table.Root>
<Table.ActionBar>
<TextInput
className="w-56"
inputProps={{"aria-label": "Search all columns"}}
onValueChange={setGlobalFilter}
placeholder="Search all columns..."
size="sm"
startIcon={Search}
value={globalFilter}
/>
<div className="text-neutral-primary font-body-sm flex items-center gap-1">
<span>Query:</span>
<span>{fetchStatus}</span>
{isFetching ? <ProgressRing className="ml-1" size="xs" /> : null}
</div>
</Table.ActionBar>
<Table.ScrollContainer>
<Table.Table>
<Table.Header>
{table.getHeaderGroups().map((headerGroup) => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<Table.HeaderCell
key={header.id}
colSpan={header.colSpan}
style={{width: header.getSize()}}
>
{header.isPlaceholder ? null : (
<div className="inline-flex min-h-[28px] w-full items-center justify-between gap-2">
<div className="inline-flex items-center gap-1">
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
<Table.ColumnSortAction header={header} />
</div>
{header.column.getCanFilter() ? (
<Popover
trigger={
<Table.ColumnFilterAction
canFilter={header.column.getCanFilter()}
isFiltered={header.column.getIsFiltered()}
/>
}
>
<TableColumnFilter
availableFilters={data?.availableFilters}
column={header.column}
columnFilters={columnFilters}
onColumnFiltersChange={setColumnFilters}
/>
</Popover>
) : null}
</div>
)}
</Table.HeaderCell>
)
})}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows.map((row) => {
return (
<Table.Row key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<Table.Cell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Table.Cell>
)
})}
</Table.Row>
)
})}
</Table.Body>
</Table.Table>
</Table.ScrollContainer>
<Table.Pagination {...paginationProps}>
<Pagination.PageMetadata>
{({count, pageEnd, pageStart}) => (
<>
{!data?.pageCount && isFetching ? (
<ProgressRing size="xs" />
) : (
`${pageStart}-${pageEnd} of ${count} results`
)}
</>
)}
</Pagination.PageMetadata>
<Pagination.PageButtons />
</Table.Pagination>
</Table.Root>
<CodeHighlight
className="w-fit"
code={JSON.stringify(
{columnFilters, globalFilter, pagination, sorting},
null,
2,
)}
disableCopy
language="json"
/>
</div>
)
}