[Experimental] Introduce `useDragHandle` hook
This PR introduces a useDragHandle hook that allows React consumers to set a drag handle in a different location than where useDraggable or useSortable is used.
Resolves https://github.com/clauderic/dnd-kit/issues/1738
Example usage — two separate components
Below the draggable is declared in a row component, while the dedicated handle lives in a different component.
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
import {useDragHandle} from '@dnd-kit/react';
import {
ColumnDef,
Row,
flexRender,
useReactTable,
getCoreRowModel,
} from '@tanstack/react-table';
// ─────────────────────────────────────────────────────
// Cell renderer — only cares about being the drag handle
// ─────────────────────────────────────────────────────
function DragCell({rowId}: {rowId: string}) {
const handleRef = useDragHandle({id: rowId}); // same id as the row
return (
<button ref={handleRef} aria-label="Drag row" style={{cursor: 'grab'}}>
⠿
</button>
);
}
// ─────────────────────────────────────────────────────
// Draggable row — owns the sortable behaviour
// ─────────────────────────────────────────────────────
function DraggableRow({row}: {row: Row<Person>}) {
const {ref} = useSortable({id: row.id, index: row.index}); // row itself is sortable
return (
<tr ref={ref}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
);
}
// ─────────────────────────────────────────────────────
// Table setup (irrelevant plumbing omitted for clarity)
// ─────────────────────────────────────────────────────
function App() {
const columns = React.useMemo<ColumnDef<Person>[]>(() => [
{ // dedicated “move” column
id: 'drag',
header: '↕︎',
size: 40,
cell: ({row}) => <DragCell rowId={row.id} />, // ← handle rendered here
},
// …other columns…
], []);
const table = useReactTable({
data, columns, getCoreRowModel: getCoreRowModel(), getRowId: r => r.id,
});
return (
<DragDropProvider>
<table>
<tbody>
{table.getRowModel().rows.map((row) => (
<DraggableRow key={row.id} row={row} />
))}
</tbody>
</table>
</DragDropProvider>
);
}
The key takeaway: useDragHandle does not need to live in the same component where useDraggable/useSortable is invoked—perfect for table rows, list items with nested actions, or any layout where the “grab-area” is rendered deeper in the tree.
⚠️ No Changeset found
Latest commit: 5b9017664ef9a0b6684823940b32bf57b13f6349
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
@dnd-kit/abstract
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/abstract@1740
@dnd-kit/collision
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/collision@1740
@dnd-kit/dom
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/dom@1740
@dnd-kit/geometry
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/geometry@1740
@dnd-kit/helpers
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/helpers@1740
@dnd-kit/react
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/react@1740
@dnd-kit/state
npm i https://pkg.pr.new/clauderic/dnd-kit/@dnd-kit/state@1740
commit: 5b90176