Documentation
Framework
Version
Enterprise

React Example: Column Dnd

tsx
import React, { CSSProperties } from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { Cell, ColumnDef, Header, flexRender, getCoreRowModel, useReactTable, } from '@tanstack/react-table' import { makeData, Person } from './makeData' // needed for table body level scope DnD setup import { DndContext, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, type DragEndEvent, useSensor, useSensors, } from '@dnd-kit/core' import { restrictToHorizontalAxis } from '@dnd-kit/modifiers' import { arrayMove, SortableContext, horizontalListSortingStrategy, } from '@dnd-kit/sortable' // needed for row & cell level scope DnD setup import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' const DraggableTableHeader = ({ header, }: { header: Header<Person, unknown> }) => { const { attributes, isDragging, listeners, setNodeRef, transform } = useSortable({ id: header.column.id, }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, position: 'relative', transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing transition: 'width transform 0.2s ease-in-out', whiteSpace: 'nowrap', width: header.column.getSize(), zIndex: isDragging ? 1 : 0, } return ( <th colSpan={header.colSpan} ref={setNodeRef} style={style}> {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} <button {...attributes} {...listeners}> 🟰 </button> </th> ) } const DragAlongCell = ({ cell }: { cell: Cell<Person, unknown> }) => { const { isDragging, setNodeRef, transform } = useSortable({ id: cell.column.id, }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, position: 'relative', transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing transition: 'width transform 0.2s ease-in-out', width: cell.column.getSize(), zIndex: isDragging ? 1 : 0, } return ( <td style={style} ref={setNodeRef}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ) } function App() { const columns = React.useMemo<ColumnDef<Person>[]>( () => [ { accessorKey: 'firstName', cell: info => info.getValue(), id: 'firstName', size: 150, }, { accessorFn: row => row.lastName, cell: info => info.getValue(), header: () => <span>Last Name</span>, id: 'lastName', size: 150, }, { accessorKey: 'age', header: () => 'Age', id: 'age', size: 120, }, { accessorKey: 'visits', header: () => <span>Visits</span>, id: 'visits', size: 120, }, { accessorKey: 'status', header: 'Status', id: 'status', size: 150, }, { accessorKey: 'progress', header: 'Profile Progress', id: 'progress', size: 180, }, ], [] ) const [data, setData] = React.useState(() => makeData(20)) const [columnOrder, setColumnOrder] = React.useState<string[]>(() => columns.map(c => c.id!) ) const rerender = () => setData(() => makeData(20)) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), state: { columnOrder, }, onColumnOrderChange: setColumnOrder, debugTable: true, debugHeaders: true, debugColumns: true, }) // reorder columns after drag & drop function handleDragEnd(event: DragEndEvent) { const { active, over } = event if (active && over && active.id !== over.id) { setColumnOrder(columnOrder => { const oldIndex = columnOrder.indexOf(active.id as string) const newIndex = columnOrder.indexOf(over.id as string) return arrayMove(columnOrder, oldIndex, newIndex) //this is just a splice util }) } } const sensors = useSensors( useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}) ) return ( // NOTE: This provider creates div elements, so don't nest inside of <table> elements <DndContext collisionDetection={closestCenter} modifiers={[restrictToHorizontalAxis]} onDragEnd={handleDragEnd} sensors={sensors} > <div className="p-2"> <div className="h-4" /> <div className="flex flex-wrap gap-2"> <button onClick={() => rerender()} className="border p-1"> Regenerate </button> </div> <div className="h-4" /> <table> <thead> {table.getHeaderGroups().map(headerGroup => ( <tr key={headerGroup.id}> <SortableContext items={columnOrder} strategy={horizontalListSortingStrategy} > {headerGroup.headers.map(header => ( <DraggableTableHeader key={header.id} header={header} /> ))} </SortableContext> </tr> ))} </thead> <tbody> {table.getRowModel().rows.map(row => ( <tr key={row.id}> {row.getVisibleCells().map(cell => ( <SortableContext key={cell.id} items={columnOrder} strategy={horizontalListSortingStrategy} > <DragAlongCell key={cell.id} cell={cell} /> </SortableContext> ))} </tr> ))} </tbody> </table> <pre>{JSON.stringify(data, null, 2)}</pre> </div> </DndContext> ) } const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( <React.StrictMode> <App /> </React.StrictMode> ) 
import React, { CSSProperties } from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { Cell, ColumnDef, Header, flexRender, getCoreRowModel, useReactTable, } from '@tanstack/react-table' import { makeData, Person } from './makeData' // needed for table body level scope DnD setup import { DndContext, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, type DragEndEvent, useSensor, useSensors, } from '@dnd-kit/core' import { restrictToHorizontalAxis } from '@dnd-kit/modifiers' import { arrayMove, SortableContext, horizontalListSortingStrategy, } from '@dnd-kit/sortable' // needed for row & cell level scope DnD setup import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' const DraggableTableHeader = ({ header, }: { header: Header<Person, unknown> }) => { const { attributes, isDragging, listeners, setNodeRef, transform } = useSortable({ id: header.column.id, }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, position: 'relative', transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing transition: 'width transform 0.2s ease-in-out', whiteSpace: 'nowrap', width: header.column.getSize(), zIndex: isDragging ? 1 : 0, } return ( <th colSpan={header.colSpan} ref={setNodeRef} style={style}> {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} <button {...attributes} {...listeners}> 🟰 </button> </th> ) } const DragAlongCell = ({ cell }: { cell: Cell<Person, unknown> }) => { const { isDragging, setNodeRef, transform } = useSortable({ id: cell.column.id, }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, position: 'relative', transform: CSS.Translate.toString(transform), // translate instead of transform to avoid squishing transition: 'width transform 0.2s ease-in-out', width: cell.column.getSize(), zIndex: isDragging ? 1 : 0, } return ( <td style={style} ref={setNodeRef}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ) } function App() { const columns = React.useMemo<ColumnDef<Person>[]>( () => [ { accessorKey: 'firstName', cell: info => info.getValue(), id: 'firstName', size: 150, }, { accessorFn: row => row.lastName, cell: info => info.getValue(), header: () => <span>Last Name</span>, id: 'lastName', size: 150, }, { accessorKey: 'age', header: () => 'Age', id: 'age', size: 120, }, { accessorKey: 'visits', header: () => <span>Visits</span>, id: 'visits', size: 120, }, { accessorKey: 'status', header: 'Status', id: 'status', size: 150, }, { accessorKey: 'progress', header: 'Profile Progress', id: 'progress', size: 180, }, ], [] ) const [data, setData] = React.useState(() => makeData(20)) const [columnOrder, setColumnOrder] = React.useState<string[]>(() => columns.map(c => c.id!) ) const rerender = () => setData(() => makeData(20)) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), state: { columnOrder, }, onColumnOrderChange: setColumnOrder, debugTable: true, debugHeaders: true, debugColumns: true, }) // reorder columns after drag & drop function handleDragEnd(event: DragEndEvent) { const { active, over } = event if (active && over && active.id !== over.id) { setColumnOrder(columnOrder => { const oldIndex = columnOrder.indexOf(active.id as string) const newIndex = columnOrder.indexOf(over.id as string) return arrayMove(columnOrder, oldIndex, newIndex) //this is just a splice util }) } } const sensors = useSensors( useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}) ) return ( // NOTE: This provider creates div elements, so don't nest inside of <table> elements <DndContext collisionDetection={closestCenter} modifiers={[restrictToHorizontalAxis]} onDragEnd={handleDragEnd} sensors={sensors} > <div className="p-2"> <div className="h-4" /> <div className="flex flex-wrap gap-2"> <button onClick={() => rerender()} className="border p-1"> Regenerate </button> </div> <div className="h-4" /> <table> <thead> {table.getHeaderGroups().map(headerGroup => ( <tr key={headerGroup.id}> <SortableContext items={columnOrder} strategy={horizontalListSortingStrategy} > {headerGroup.headers.map(header => ( <DraggableTableHeader key={header.id} header={header} /> ))} </SortableContext> </tr> ))} </thead> <tbody> {table.getRowModel().rows.map(row => ( <tr key={row.id}> {row.getVisibleCells().map(cell => ( <SortableContext key={cell.id} items={columnOrder} strategy={horizontalListSortingStrategy} > <DragAlongCell key={cell.id} cell={cell} /> </SortableContext> ))} </tr> ))} </tbody> </table> <pre>{JSON.stringify(data, null, 2)}</pre> </div> </DndContext> ) } const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( <React.StrictMode> <App /> </React.StrictMode> )