tanstack-table

安装量: 126
排名: #10346

安装

npx skills add https://github.com/tanstack-skills/tanstack-skills --skill tanstack-table

Overview TanStack Table is a headless UI library for building data tables and datagrids. It provides logic for sorting, filtering, pagination, grouping, expanding, column pinning/ordering/visibility/resizing, and row selection - without rendering any markup or styles. Package: @tanstack/react-table Utilities: @tanstack/match-sorter-utils (fuzzy filtering) Current Version: v8 Installation npm install @tanstack/react-table Core Architecture Building Blocks Column Definitions - describe columns (data access, rendering, features) Table Instance - central coordinator with state and APIs Row Models - data processing pipeline (filter -> sort -> group -> paginate) Headers, Rows, Cells - renderable units Critical: Data & Column Stability // WRONG - new references every render, causes infinite loops const table = useReactTable ( { data : fetchedData . results , // new ref! columns : [ { accessorKey : 'name' } ] , // new ref! } ) // CORRECT - stable references const columns = useMemo ( ( ) => [ ... ] , [ ] ) const data = useMemo ( ( ) => fetchedData ?. results ?? [ ] , [ fetchedData ] ) const table = useReactTable ( { data , columns , getCoreRowModel : getCoreRowModel ( ) } ) Column Definitions Using createColumnHelper (Recommended) import { createColumnHelper } from '@tanstack/react-table' type Person = { firstName : string lastName : string age : number status : 'active' | 'inactive' } const columnHelper = createColumnHelper < Person

( ) const columns = [ // Accessor column (data column) columnHelper . accessor ( 'firstName' , { header : 'First Name' , cell : info => info . getValue ( ) , footer : info => info . column . id , } ) , // Accessor with function columnHelper . accessor ( row => row . lastName , { id : 'lastName' , // required with accessorFn header : ( ) => < span

Last Name < / span

, cell : info => < i

{ info . getValue ( ) } < / i

, } ) , // Display column (no data, custom rendering) columnHelper . display ( { id : 'actions' , header : 'Actions' , cell : ( { row } ) => ( < button onClick = { ( ) => deleteRow ( row . original ) }

Delete < / button

) , } ) , // Group column (nested headers) columnHelper . group ( { id : 'info' , header : 'Info' , columns : [ columnHelper . accessor ( 'age' , { header : 'Age' } ) , columnHelper . accessor ( 'status' , { header : 'Status' } ) , ] , } ) , ] Column Options Option Type Description id string Unique identifier (auto-derived from accessorKey) accessorKey string Dot-notation path to row data accessorFn (row) => any Custom accessor function header string | (context) => ReactNode Header renderer cell (context) => ReactNode Cell renderer footer (context) => ReactNode Footer renderer size number Default width (default: 150) minSize number Min width (default: 20) maxSize number Max width enableSorting boolean Enable sorting sortingFn string | SortingFn Sort function enableFiltering boolean Enable filtering filterFn string | FilterFn Filter function enableGrouping boolean Enable grouping aggregationFn string | AggregationFn Aggregation function enableHiding boolean Enable visibility toggle enableResizing boolean Enable resizing enablePinning boolean Enable pinning meta any Custom metadata Table Instance Creating a Table import { useReactTable , getCoreRowModel , getSortedRowModel , getFilteredRowModel , getPaginationRowModel , flexRender , } from '@tanstack/react-table' function MyTable ( ) { const [ sorting , setSorting ] = useState < SortingState

( [ ] ) const [ columnFilters , setColumnFilters ] = useState < ColumnFiltersState

( [ ] ) const [ pagination , setPagination ] = useState < PaginationState

( { pageIndex : 0 , pageSize : 10 , } ) const table = useReactTable ( { data , columns , state : { sorting , columnFilters , pagination } , onSortingChange : setSorting , onColumnFiltersChange : setColumnFilters , onPaginationChange : setPagination , getCoreRowModel : getCoreRowModel ( ) , getSortedRowModel : getSortedRowModel ( ) , getFilteredRowModel : getFilteredRowModel ( ) , getPaginationRowModel : getPaginationRowModel ( ) , } ) return ( < table

< thead

{ table . getHeaderGroups ( ) . map ( headerGroup => ( < tr key = { headerGroup . id }

{ headerGroup . headers . map ( header => ( < th key = { header . id } onClick = { header . column . getToggleSortingHandler ( ) }

{ header . isPlaceholder ? null : flexRender ( header . column . columnDef . header , header . getContext ( ) ) } { { asc : ' ↑' , desc : ' ↓' } [ header . column . getIsSorted ( ) as string ] ?? null } < / th

) ) } < / tr

) ) } < / thead

< tbody

{ table . getRowModel ( ) . rows . map ( row => ( < tr key = { row . id }

{ row . getVisibleCells ( ) . map ( cell => ( < td key = { cell . id }

{ flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) } < / td

) ) } < / tr

) ) } < / tbody

< / table

) } Sorting const table = useReactTable ( { state : { sorting } , onSortingChange : setSorting , getSortedRowModel : getSortedRowModel ( ) , enableSorting : true , enableMultiSort : true , // manualSorting: true, // For server-side sorting } ) // Built-in sort functions: 'alphanumeric', 'text', 'datetime', 'basic' // Column-level: sortingFn: 'alphanumeric' Filtering Column Filtering const table = useReactTable ( { state : { columnFilters } , onColumnFiltersChange : setColumnFilters , getFilteredRowModel : getFilteredRowModel ( ) , getFacetedRowModel : getFacetedRowModel ( ) , getFacetedUniqueValues : getFacetedUniqueValues ( ) , getFacetedMinMaxValues : getFacetedMinMaxValues ( ) , } ) // Built-in: 'includesString', 'equalsString', 'arrIncludes', 'inNumberRange', etc. // Filter UI function Filter ( { column } ) { return ( < input value = { ( column . getFilterValue ( ) ?? '' ) as string } onChange = { e => column . setFilterValue ( e . target . value ) } placeholder = { Filter... ( ${ column . getFacetedUniqueValues ( ) ?. size } ) } /

) } Global Filtering const [ globalFilter , setGlobalFilter ] = useState ( '' ) const table = useReactTable ( { state : { globalFilter } , onGlobalFilterChange : setGlobalFilter , globalFilterFn : 'includesString' , getFilteredRowModel : getFilteredRowModel ( ) , } ) Fuzzy Filtering import { rankItem } from '@tanstack/match-sorter-utils' const fuzzyFilter : FilterFn < any

= ( row , columnId , value , addMeta ) => { const itemRank = rankItem ( row . getValue ( columnId ) , value ) addMeta ( { itemRank } ) return itemRank . passed } const table = useReactTable ( { filterFns : { fuzzy : fuzzyFilter } , globalFilterFn : 'fuzzy' , } ) Pagination const table = useReactTable ( { state : { pagination } , onPaginationChange : setPagination , getPaginationRowModel : getPaginationRowModel ( ) , // For server-side: // manualPagination: true, // pageCount: serverPageCount, } ) // Navigation table . nextPage ( ) table . previousPage ( ) table . firstPage ( ) table . lastPage ( ) table . setPageSize ( 20 ) table . getCanNextPage ( ) // boolean table . getCanPreviousPage ( ) // boolean table . getPageCount ( ) // total pages Row Selection const [ rowSelection , setRowSelection ] = useState < RowSelectionState

( { } ) const table = useReactTable ( { state : { rowSelection } , onRowSelectionChange : setRowSelection , enableRowSelection : true , enableMultiRowSelection : true , } ) // Checkbox column columnHelper . display ( { id : 'select' , header : ( { table } ) => ( < input type = "checkbox" checked = { table . getIsAllRowsSelected ( ) } onChange = { table . getToggleAllRowsSelectedHandler ( ) } /

) , cell : ( { row } ) => ( < input type = "checkbox" checked = { row . getIsSelected ( ) } disabled = { ! row . getCanSelect ( ) } onChange = { row . getToggleSelectedHandler ( ) } /

) , } ) // Get selected rows table . getSelectedRowModel ( ) . rows Column Visibility const [ columnVisibility , setColumnVisibility ] = useState < VisibilityState

( { } ) const table = useReactTable ( { state : { columnVisibility } , onColumnVisibilityChange : setColumnVisibility , } ) // Toggle UI { table . getAllLeafColumns ( ) . map ( column => ( < label key = { column . id }

< input type = "checkbox" checked = { column . getIsVisible ( ) } onChange = { column . getToggleVisibilityHandler ( ) } /

{ column . id } < / label

) ) } Column Pinning const [ columnPinning , setColumnPinning ] = useState < ColumnPinningState

( { left : [ 'select' , 'name' ] , right : [ 'actions' ] , } ) const table = useReactTable ( { state : { columnPinning } , onColumnPinningChange : setColumnPinning , enableColumnPinning : true , } ) // Render pinned sections separately row . getLeftVisibleCells ( ) // Left-pinned row . getCenterVisibleCells ( ) // Unpinned row . getRightVisibleCells ( ) // Right-pinned Column Resizing const table = useReactTable ( { enableColumnResizing : true , columnResizeMode : 'onChange' , // 'onChange' | 'onEnd' defaultColumn : { size : 150 , minSize : 50 , maxSize : 500 } , } ) // Resize handle in header < div onMouseDown = { header . getResizeHandler ( ) } onTouchStart = { header . getResizeHandler ( ) } className = { resizer ${ header . column . getIsResizing ( ) ? 'isResizing' : '' } } /

Grouping & Aggregation const [ grouping , setGrouping ] = useState < GroupingState

( [ ] ) const table = useReactTable ( { state : { grouping } , onGroupingChange : setGrouping , getGroupedRowModel : getGroupedRowModel ( ) , getExpandedRowModel : getExpandedRowModel ( ) , } ) // Built-in aggregation: 'sum', 'min', 'max', 'mean', 'median', 'count', 'unique', 'uniqueCount' columnHelper . accessor ( 'amount' , { aggregationFn : 'sum' , aggregatedCell : ( { getValue } ) => Total: ${ getValue ( ) } , } ) Row Expanding const [ expanded , setExpanded ] = useState < ExpandedState

( { } ) const table = useReactTable ( { state : { expanded } , onExpandedChange : setExpanded , getExpandedRowModel : getExpandedRowModel ( ) , getSubRows : ( row ) => row . subRows , // For hierarchical data } ) // Expand toggle < button onClick = { row . getToggleExpandedHandler ( ) }

{ row . getIsExpanded ( ) ? '−' : '+' } < / button

// Detail row pattern { row . getIsExpanded ( ) && ( < tr

< td colSpan = { columns . length }

< DetailComponent data = { row . original } /

< / td

< / tr

) } Virtualization Integration import { useVirtualizer } from '@tanstack/react-virtual' function VirtualizedTable ( ) { const table = useReactTable ( { / ... / } ) const { rows } = table . getRowModel ( ) const parentRef = useRef < HTMLDivElement

( null ) const virtualizer = useVirtualizer ( { count : rows . length , getScrollElement : ( ) => parentRef . current , estimateSize : ( ) => 35 , overscan : 10 , } ) return ( < div ref = { parentRef } style = { { height : '600px' , overflow : 'auto' } }

< table

< tbody style = { { height : ${ virtualizer . getTotalSize ( ) } px , position : 'relative' } }

{ virtualizer . getVirtualItems ( ) . map ( virtualRow => { const row = rows [ virtualRow . index ] return ( < tr key = { row . id } style = { { position : 'absolute' , transform : translateY( ${ virtualRow . start } px) , height : ${ virtualRow . size } px , } }

{ row . getVisibleCells ( ) . map ( cell => ( < td key = { cell . id }

{ flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) } < / td

) ) } < / tr

) } ) } < / tbody

< / table

< / div

) } Server-Side Operations const table = useReactTable ( { data : serverData , columns , manualSorting : true , manualFiltering : true , manualPagination : true , pageCount : serverPageCount , state : { sorting , columnFilters , pagination } , onSortingChange : setSorting , onColumnFiltersChange : setColumnFilters , onPaginationChange : setPagination , getCoreRowModel : getCoreRowModel ( ) , // Do NOT include getSortedRowModel, getFilteredRowModel, getPaginationRowModel } ) // Fetch data based on state useEffect ( ( ) => { fetchData ( { sorting , filters : columnFilters , pagination } ) } , [ sorting , columnFilters , pagination ] ) TypeScript Patterns Extending Column Meta declare module '@tanstack/react-table' { interface ColumnMeta < TData extends RowData , TValue

{ filterVariant ? : 'text' | 'range' | 'select' align ? : 'left' | 'center' | 'right' } } Custom Filter/Sort Function Registration declare module '@tanstack/react-table' { interface FilterFns { fuzzy : FilterFn < unknown

} interface SortingFns { myCustomSort : SortingFn < unknown

} } Editable Cells via Table Meta declare module '@tanstack/react-table' { interface TableMeta < TData extends RowData

{ updateData : ( rowIndex : number , columnId : string , value : unknown ) => void } } const table = useReactTable ( { meta : { updateData : ( rowIndex , columnId , value ) => { setData ( old => old . map ( ( row , i ) => i === rowIndex ? { ... row , [ columnId ] : value } : row ) ) } , } , } ) Key Imports import { createColumnHelper , flexRender , useReactTable , getCoreRowModel , getSortedRowModel , getFilteredRowModel , getPaginationRowModel , getGroupedRowModel , getExpandedRowModel , getFacetedRowModel , getFacetedUniqueValues , getFacetedMinMaxValues , } from '@tanstack/react-table' import type { ColumnDef , SortingState , ColumnFiltersState , VisibilityState , PaginationState , ExpandedState , RowSelectionState , GroupingState , ColumnOrderState , ColumnPinningState , FilterFn , SortingFn , } from '@tanstack/react-table' Best Practices Always memoize data and columns to prevent infinite re-renders Use flexRender for all header/cell/footer rendering Use table.getRowModel().rows for final rendered rows (not getCoreRowModel) Import only needed row models - each adds processing to the pipeline Use getRowId for stable row keys when data has unique IDs Use manualX options for server-side operations Pair controlled state with both state.X and onXChange Use module augmentation for custom meta, filter fns, sort fns Use column helper for type-safe column definitions Set autoResetPageIndex: true when filtering should reset pagination Common Pitfalls Defining columns inline (creates new ref each render) Forgetting getCoreRowModel() (required for all tables) Using row models without importing them Not providing id when using accessorFn Mixing manualPagination with client-side getPaginationRowModel Forgetting colSpan for grouped headers Not handling header.isPlaceholder for group column spacers

返回排行榜