Column filtering and sorting
Control text, number, date, and boolean filters while retaining filters and source data after sorting.
Expected behavior
Section titled “Expected behavior”Each preset replaces the complete filter model. Sorting retains the filter. Use a header filter button to edit conditions directly.
| Action | Visible rows |
|---|---|
| Initial state | Alpha, Beta, Gamma, Delta |
| Name starts with g | Gamma — text comparison is case-insensitive by default. |
| Amount 100–200 | Alpha(120), Delta(160) — inclusive endpoints. |
| Joined in February | Beta, Gamma — UTC calendar dates. |
| Active AND amount > 150 | Gamma, Delta — rules for different columns combine with AND. |
| Amount 100–200 → Amount descending | Delta, Alpha — retains the filter model and original row array. |
| Reset | Clears all filters and sorting. |
Wire the state
Section titled “Wire the state”- Set
columns[].filter.kindfor each value type. This example provides stable column and row IDs. - Own
columnFiltering.modelandopenColumnIdin React state. onChangeModelsupplies the complete next model;onChangeOpenColumnIdsupplies the next open column ID or null. Connect both to their state setters.- Pass source
dataunchanged. The package projects matching rows and then sorts the result.
Filtering is controlled. Do not add a sortModel prop to this version’s CominsTable. The sort button calls ref.setSortModel() and onChangeSortModel observes changes. The displayed sort JSON comes from that notification.
Edit a condition
Section titled “Edit a condition”Open the Name header’s filter icon, enter zz in Value, and see the empty result update immediately. Escape closes the editor. Reset reveals all four original rows.
Inputs and boundaries
Section titled “Inputs and boundaries”- Pass numbers for numeric
betweenvalues and exactYYYY-MM-DDstrings for date values. This does not compare a datetime’s local calendar date as written. - Source data is unchanged. Filtering and sorting affect the visible projection.
- This is a CSR flat-data example. Do not combine it with Tree, remote Viewport, Infinite Scroll, or Lazy Load. Row Drag is unavailable when
columnFilteringexists. - Row Grouping, Summary Row, and exhaustive operator combinations remain separate example work.
Related documentation
Section titled “Related documentation”Column filtering · Sort ref method · Selection and sorting state
Try it live
Section titled “Try it live”import { useMemo, useRef, useState } from 'react';import { CominsTable, type CominsTableColumn, type CominsTableRef, type CominsColumnFilterModel, type CominsSortModel } from 'comins-table';import 'comins-table/styles.css';
type Row = { id: string; name: string; amount: number; joinedAt: string; active: boolean };const rows: readonly Row[] = [ { id: 'alpha', name: 'Alpha', amount: 120, joinedAt: '2026-01-10', active: true }, { id: 'beta', name: 'Beta', amount: 80, joinedAt: '2026-02-12', active: false }, { id: 'gamma', name: 'Gamma', amount: 240, joinedAt: '2026-02-20', active: true }, { id: 'delta', name: 'Delta', amount: 160, joinedAt: '2026-03-05', active: true },];
export default function TableFilteringDemo({ lang = 'ko' }: { lang?: string }) { const ko = lang === 'ko'; const table = useRef<CominsTableRef<Row>>(null); const [model, setModel] = useState<CominsColumnFilterModel>([]); const [openColumnId, setOpenColumnId] = useState<string | null>(null); const [observedSort, setObservedSort] = useState<CominsSortModel>([]); const columns = useMemo<CominsTableColumn<Row>[]>(() => [ { id: 'name', field: 'name', label: ko ? '이름' : 'Name', width: 150, filter: { kind: 'text' }, sort: true }, { id: 'amount', field: 'amount', label: ko ? '금액' : 'Amount', width: 130, filter: { kind: 'number' }, sort: true }, { id: 'joinedAt', field: 'joinedAt', label: ko ? '가입일' : 'Joined', width: 150, filter: { kind: 'date' }, sort: true }, { id: 'active', field: 'active', label: ko ? '활성' : 'Active', width: 110, filter: { kind: 'boolean' } }, ], [ko]); const preset = (next: CominsColumnFilterModel) => { setModel(next); setOpenColumnId(null); };
return <div className="demo" data-demo="table-filtering"> <div className="demo-toolbar"> <button onClick={() => preset([{ columnId: 'name', operator: 'startsWith', value: 'g' }])}>{ko ? '이름 g로 시작' : 'Name starts with g'}</button> <button onClick={() => preset([{ columnId: 'amount', operator: 'between', value: 100, valueTo: 200 }])}>{ko ? '금액 100–200' : 'Amount 100–200'}</button> <button onClick={() => preset([{ columnId: 'joinedAt', operator: 'between', value: '2026-02-01', valueTo: '2026-02-28' }])}>{ko ? '2월 가입' : 'Joined in February'}</button> <button onClick={() => preset([{ columnId: 'active', operator: 'equals', value: true }, { columnId: 'amount', operator: 'greaterThan', value: 150 }])}>{ko ? '활성 AND 금액 > 150' : 'Active AND amount > 150'}</button> <button onClick={() => table.current?.setSortModel([{ columnId: 'amount', direction: 'desc' }])}>{ko ? '금액 내림차순' : 'Amount descending'}</button> <button onClick={() => { preset([]); table.current?.clearSort(); }}>{ko ? '초기화' : 'Reset'}</button> </div> <div className="table-viewport"> <CominsTable ref={table} columns={columns} data={rows} getRowId={row => row.id} columnFiltering={{ model, openColumnId, onChangeModel: setModel, onChangeOpenColumnId: setOpenColumnId }} onChangeSortModel={setObservedSort} persistHeaderWhenEmpty emptyContent={{ render: () => <span>{ko ? '조건에 맞는 행이 없습니다.' : 'No matching rows.'}</span> }} /> </div> <p className="demo-status">{ko ? '원본 데이터는 항상 4행입니다. 헤더의 필터 버튼으로 조건을 직접 편집할 수 있습니다.' : 'Source data always contains 4 rows. Edit conditions with the header filter buttons.'}</p> <details open><summary>{ko ? '소유한 필터 모델' : 'Owned filter model'}</summary><pre data-filter-model>{JSON.stringify(model, null, 2)}</pre></details> <details><summary>{ko ? '정렬 변경 알림' : 'Observed sort changes'}</summary><pre data-sort-model>{JSON.stringify(observedSort, null, 2)}</pre></details> <details><summary>{ko ? '원본 행 순서' : 'Source row order'}</summary><pre data-source-ids>{rows.map(row => row.id).join(', ')}</pre></details> </div>;}