Skip to content

Changelog

Latest updates and release notes for Niko Table.

  • getRowMemoKey prop — all six body components (DataTableBody, DataTableVirtualizedBody, DataTableDndBody, DataTableDndColumnBody, DataTableVirtualizedDndBody, DataTableVirtualizedDndColumnBody) now accept getRowMemoKey?: (row: TData) => string. Return a string per row that encodes external row-level state (inline edit draft values, optimistic overlays, saving spinners). When the string changes for a row, React.memo re-renders only that row — without this, closures captured in column cell definitions become stale. See the Inline Edit Table example.
  • DataTableVirtualizedFlexHeader — flex-layout header for DataTableVirtualizedDndBody. Pick by body: plain → DataTableVirtualizedHeader, row-DnD → DataTableVirtualizedFlexHeader, column-DnD → DataTableVirtualizedDndHeader.
  • Options with count: 0 are hidden across every filter UI (faceted filter, filter menu, inline filter). Server-side tables that pass cross-filter counts get automatic narrowing without writing helpers. Pure label-only callers (no counts) unaffected. Opt-out per option by passing count: undefined.
  • autoResetPageIndex defaults to false (was true). Sort/filter changes now preserve the pagination cursor. Opt back in via config={{ autoResetPageIndex: true }}.
  • TableRangeFilter[min, max] memo depends on faceted scalars, so the range refreshes on data change. formatValue no longer locale-formats numbers (type="number" inputs reject localized output).
  • TableSliderFilter clear button — always stopPropagation() (was DIV-only, so SVG/icon clicks bubbled and re-opened the popover).
  • Sort / filter / inline-filter column memos — now key on table.options.columns (table ref alone is too stable).
  • Expanded-row re-measure effectrowMemoKey is now in the useEffect dependency array of virtualized body rows so height is re-measured when inline edit state changes while a row is expanded.
  • TableSortMenu — derives the next sorting from table.getState().sorting instead of the closure-captured sorting.
  • CSV export — plain objects are now JSON-encoded instead of serializing as [object Object].
  • Filter menu / inline filter — stale counts on filter changes. <DataTableFilterMenu /> and <DataTableInlineFilter /> mutate meta.options to inject counts; once pinned, those counts survived later filter changes so the count-0 hide rule never fired (e.g. selecting Category=Clothing left every Brand option visible). Pristine options are now captured in a ref and meta.options is rebuilt from that source each render — cross-filter narrowing now works in both filter UIs.
  • Faceted filter — caller-supplied options no longer narrowed to current row set; caller is the source of truth.
  • TableColumnFacetedFilterMenulimitToFilteredRows decoupled from multiple default for caller options.
  • Cross-table “state update on unmounted component” warning silenced via mount-ref guards on every default state setter.
  • onRowClick event type unified to React.MouseEvent<HTMLElement> across all bodies.
  • Virtualized DnD body — expanded-row height re-measured on toggle without losing useSortable registration.
  • Virtualized non-DnD body — setColumnsLocked race fixed; columnsLocked gates measureElement.
  • Virtualized bodies — data-index uses virtualRow.index; click delegation via stable data-row-id + table.getRow(rowId).
  • Virtualized bodies — expanded-row height included in virtualizer measurement.
  • Virtualized Row-DnD body — selected row styling applies (data-state="selected").
  • Virtualized DnD bodies — onRowClick widened to React.MouseEvent<HTMLElement>.
  • Column-DnD bodies (virtualized + non-virtualized) — expanded-row rendering matched to other bodies.
  • VirtualizedDraggableRow — added data-index alongside data-row-index.
  • FILTER_OPERATORS.RELATIVE hidden from the date-filter dropdown (kept in catalogue for server-side consumers); per-row console.error throttled.
  • getObjectHash — full sorted-keys join (was first-3 keys, false-negatived row selection with sequential IDs).
  • Scroll handler rAF-coalesced; onScrolledTop / onScrolledBottom fire only on the leading edge (false→true).
  • Row-click guard hardened — suppresses clicks during active text selection; recognizes textarea, select, label, [contenteditable], and ARIA combobox/menuitem/textbox.
  • Slider filters — facetedMin/facetedMax hoisted into memo deps so [min, max] stays reactive on data change.
  • TableViewMenu visible-columns memo keys on table.options.columns (was just table ref).
  • DataTableBody — row clicks delegated to <tbody>, removing one listener per row.
  • tableOptions — 6 inline fallback setters extracted to stable useCallbacks; deps trimmed to destructured state / initialState / globalFilterFn.
  • expandColumnId memoized in all six bodies (was O(rows × cols) per render).
  • DataTableColumnHeaderRoot context value memoized.
  • useKeyboardShortcuts listener no longer re-attaches every render.
  • onRowSelection no longer fires on initial mount — user-driven changes only.
  • handleRowSelectionChange honors full Updater<T> contract; side effects moved to useEffect.
  • DataTableLoading self-gates on isLoading.
  • useGeneratedOptions — eliminated double getFilteredRowsExcludingColumn walk.
  • Row-click guard moved to lib/row-click.ts (was inlined in 6 places); body-scroll listener extracted to lib/create-scroll-handler.ts.
  • data-table-virtualized-dnd-structure.tsx marked @internal.
  • DnD virtualized bodies don’t gate measureElement on columnsLocked — flex layout has no auto-layout pass.
  • Virtualized table — column spacing no longer compresses on wide datasets. The column-lock effect now sets tableEl.style.minWidth to the sum of visible column.getSize() values before measuring, so the table can expand beyond its scroll container instead of being squeezed by w-full.
  • DataTableLoadingMore and DataTableVirtualizedLoadingMore — composable “loading more” rows for infinite-scroll tables. Self-gate on isFetching.
  • onNearEnd + prefetchThreshold on DataTableVirtualizedBody — virtualizer-index-driven prefetch trigger for infinite scroll. Strictly better than onScrolledBottom: catches fast scrolls, scrollbar drag, scrollToIndex() jumps, and short initial renders.
  • Virtualized table — switched to native <table> layout with measure-and-lock column sizing. The browser distributes column widths from content; DataTableVirtualizedBody measures each <th> after first data render and locks to table-layout: fixed.
  • Scroll listeneronScrolledBottom / onScrolledTop now wire up even when onScroll isn’t provided (was silently dead in all four bodies).
  • Faceted filter — dynamicCounts honored on every code path (was silently ignored in fallback useMemo and the auto-generated branch).
  • Faceted filter — caller-supplied options go through count enrichment instead of short-circuiting.
  • Faceted filter — TableColumnFacetedFilterMenu forwards dynamicCounts to the underlying hook.
  • Faceted filter — zero-count options no longer disappear.
  • Faceted filter — multi-select defaults limitToFilteredRows to false (single-select keeps true). Expressed as limitToFilteredRows ??= !multiple.
  • preserve merge strategy — returns user-defined meta.options untouched. Only augment injects counts.
  • Vitest introduced. 23 tests covering every (limitToFilteredRows × dynamicCounts) permutation, merge-strategy branches, zero-count options, and prop-wiring regressions.
  • GitHub Actions CI runs pnpm test, eslint, and pnpm build on every PR and push to main.
  • New example pages: Infinite Scroll Table (non-virtualized) and Infinite Scroll Virtualized Table.
  • Core overview reference adds DataTableLoadingMore, DataTableVirtualizedLoadingMore, onNearEnd, prefetchThreshold.

  • Row click delegated at <tbody> instead of per-row across all six body components.
  • DataTableRoot, TablePagination, DataTableAside — handlers memoized with useCallback for stable references.
  • TablePagination — page input uses local draft state. Page index commits on blur or Enter.
  • TablePaginationonPageSizeChange now receives the recalculated page index.
  • Sticky header z-index — pinned body cells no longer overlap the sticky header (z-30 > z-20 > z-10).
  • Pinned header cells get top: 0 so they stick vertically when the header is sticky.
  • DataTableRoot — docs list the required children prop and the optional state prop.

The first public release of Niko Table — a composable, shadcn-compatible data table component registry built with TanStack Table and React.

  • DataTable - Core data table with sorting, filtering, and pagination
  • DataTableVirtualized - Virtualized rendering for large datasets
  • DataTablePagination - Flexible pagination controls
  • DataTableSearchFilter - Global search across all columns
  • DataTableFacetedFilter - Multi-select faceted filtering with counts
  • DataTableFilterMenu - Advanced filter menu with AND/OR logic
  • DataTableInlineFilter - Inline filter bar with mixed operator support
  • DataTableSliderFilter - Range slider filtering for numeric columns
  • DataTableDateFilter - Date and date range filtering
  • DataTableExportButton - CSV/JSON export
  • DataTableAside - Side panel for row details
  • DataTableSelectionBar - Bulk actions on selected rows
  • DataTableColumnSort - Sort menu and sort icon components
  • DataTableColumnHide - Column visibility toggle
  • DataTableColumnPin - Column pinning (left/right)
  • Row DnD - Drag and drop row reordering via @dnd-kit
  • Column DnD - Drag and drop column reordering
  • Row Selection - Single and multi-row selection with bulk actions
  • Row Expansion - Expandable rows with custom content
  • Tree Table - Hierarchical data with expand/collapse
  • Virtualization - Smooth scrolling for 10k+ rows
  • URL State - Sync filters, sorting, and pagination to URL via nuqs
  • Server-Side - Server-side filtering, sorting, and pagination support
  • 15+ filter operators (contains, equals, greater than, between, etc.)
  • Mixed AND/OR logic with mathematical precedence
  • Per-filter join operator control
  • Regex-cached filter execution for large datasets