Documentation
Framework
Version
Enterprise

React Example: Row Dnd

import React, { CSSProperties } from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { ColumnDef, Row, 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, type UniqueIdentifier, useSensor, useSensors, } from '@dnd-kit/core' import { restrictToVerticalAxis } from '@dnd-kit/modifiers' import { arrayMove, SortableContext, verticalListSortingStrategy, } from '@dnd-kit/sortable' // needed for row & cell level scope DnD setup import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' // Cell Component const RowDragHandleCell = ({ rowId }: { rowId: string }) => { const { attributes, listeners } = useSortable({ id: rowId, }) return ( // Alternatively, you could set these attributes on the rows themselves <button {...attributes} {...listeners}> 🟰 </button> ) } // Row Component const DraggableRow = ({ row }: { row: Row<Person> }) => { const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original.userId, }) const style: CSSProperties = { transform: CSS.Transform.toString(transform), //let dnd-kit do its thing transition: transition, opacity: isDragging ? 0.8 : 1, zIndex: isDragging ? 1 : 0, position: 'relative', } return ( // connect row ref to dnd-kit, apply important styles <tr ref={setNodeRef} style={style}> {row.getVisibleCells().map(cell => ( <td key={cell.id} style={{ width: cell.column.getSize() }}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ) } // Table Component function App() { const columns = React.useMemo<ColumnDef<Person>[]>( () => [ // Create a dedicated drag handle column. Alternatively, you could just set up dnd events on the rows themselves. { id: 'drag-handle', header: 'Move', cell: ({ row }) => <RowDragHandleCell rowId={row.id} />, size: 60, }, { accessorKey: 'firstName', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => <span>Last Name</span>, }, { accessorKey: 'age', header: () => 'Age', }, { accessorKey: 'visits', header: () => <span>Visits</span>, }, { accessorKey: 'status', header: 'Status', }, { accessorKey: 'progress', header: 'Profile Progress', }, ], [] ) const [data, setData] = React.useState(() => makeData(20)) const dataIds = React.useMemo<UniqueIdentifier[]>( () => data?.map(({ userId }) => userId), [data] ) const rerender = () => setData(() => makeData(20)) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getRowId: row => row.userId, //required because row indexes will change debugTable: true, debugHeaders: true, debugColumns: true, }) // reorder rows after drag & drop function handleDragEnd(event: DragEndEvent) { const { active, over } = event if (active && over && active.id !== over.id) { setData(data => { const oldIndex = dataIds.indexOf(active.id) const newIndex = dataIds.indexOf(over.id) return arrayMove(data, 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={[restrictToVerticalAxis]} 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}> {headerGroup.headers.map(header => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} </th> ))} </tr> ))} </thead> <tbody> <SortableContext items={dataIds} strategy={verticalListSortingStrategy} > {table.getRowModel().rows.map(row => ( <DraggableRow key={row.id} row={row} /> ))} </SortableContext> </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 { ColumnDef, Row, 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, type UniqueIdentifier, useSensor, useSensors, } from '@dnd-kit/core' import { restrictToVerticalAxis } from '@dnd-kit/modifiers' import { arrayMove, SortableContext, verticalListSortingStrategy, } from '@dnd-kit/sortable' // needed for row & cell level scope DnD setup import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' // Cell Component const RowDragHandleCell = ({ rowId }: { rowId: string }) => { const { attributes, listeners } = useSortable({ id: rowId, }) return ( // Alternatively, you could set these attributes on the rows themselves <button {...attributes} {...listeners}> 🟰 </button> ) } // Row Component const DraggableRow = ({ row }: { row: Row<Person> }) => { const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original.userId, }) const style: CSSProperties = { transform: CSS.Transform.toString(transform), //let dnd-kit do its thing transition: transition, opacity: isDragging ? 0.8 : 1, zIndex: isDragging ? 1 : 0, position: 'relative', } return ( // connect row ref to dnd-kit, apply important styles <tr ref={setNodeRef} style={style}> {row.getVisibleCells().map(cell => ( <td key={cell.id} style={{ width: cell.column.getSize() }}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ) } // Table Component function App() { const columns = React.useMemo<ColumnDef<Person>[]>( () => [ // Create a dedicated drag handle column. Alternatively, you could just set up dnd events on the rows themselves. { id: 'drag-handle', header: 'Move', cell: ({ row }) => <RowDragHandleCell rowId={row.id} />, size: 60, }, { accessorKey: 'firstName', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => <span>Last Name</span>, }, { accessorKey: 'age', header: () => 'Age', }, { accessorKey: 'visits', header: () => <span>Visits</span>, }, { accessorKey: 'status', header: 'Status', }, { accessorKey: 'progress', header: 'Profile Progress', }, ], [] ) const [data, setData] = React.useState(() => makeData(20)) const dataIds = React.useMemo<UniqueIdentifier[]>( () => data?.map(({ userId }) => userId), [data] ) const rerender = () => setData(() => makeData(20)) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getRowId: row => row.userId, //required because row indexes will change debugTable: true, debugHeaders: true, debugColumns: true, }) // reorder rows after drag & drop function handleDragEnd(event: DragEndEvent) { const { active, over } = event if (active && over && active.id !== over.id) { setData(data => { const oldIndex = dataIds.indexOf(active.id) const newIndex = dataIds.indexOf(over.id) return arrayMove(data, 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={[restrictToVerticalAxis]} 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}> {headerGroup.headers.map(header => ( <th key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} </th> ))} </tr> ))} </thead> <tbody> <SortableContext items={dataIds} strategy={verticalListSortingStrategy} > {table.getRowModel().rows.map(row => ( <DraggableRow key={row.id} row={row} /> ))} </SortableContext> </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> ) 
Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.

Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.