React/AntDesign How to make rows draggable? (Table Drag Sorting) React/AntDesign How to make rows draggable? (Table Drag Sorting) reactjs reactjs

React/AntDesign How to make rows draggable? (Table Drag Sorting)


Here is a simple example I came up with based off of a lot of different things I found online and eventually formulated something that worked for my code:

Basically this is the body of a react component table getting called and I am mapping a list of places via the indexes and each row is draggable to allow the user to modify the table. I have also included the function calls associated with the attributes

return (        <Tbody>            {items.map((item, index) => (                    <Tr key={index} className="item.name"                        draggable={true}                        onDragStart={this.dragstart}                        onDragEnd={this.dragend}                        onDragLeave={this.dragend}                        onDragEnter={this.dragend}                        onDrop={this.drop}                        onDragOver={this.dragend}                        id={index} >                        <Td><span>{item.name}</span></Td>                        <Td><span>{item.latitude}</span></Td>                        <Td><span>{item.longitude}</span></Td>                        <Td type="data"><span>{this.checkForZero(this.props.propDistances[index])}</span></Td>                        <Td type="data"><span>{this.checkForZero(this.props.propCumulativeDist[index])}</span></Td>                    </Tr>            ))}            {this.finishItineraryTable()}        </Tbody>    );dragstart(event) {    let dataTransfer = event.dataTransfer;    let node = event.target;    dataTransfer.setData('text/plain',node.innerHTML);    dataTransfer.setData('id',node.id);    event.stopPropagation();}dragend(event) {    event.preventDefault();}drop(event) {    event.preventDefault();    let dragObjHtml = event.dataTransfer.getData("text/plain");    let dragObjId = document.getElementById(event.dataTransfer.getData("id"));    let dropTarget = event.target.closest("tr");    let temp = dropTarget.innerHTML;    dropTarget.innerHTML = dragObjHtml;    dragObjId.innerHTML = temp;}


Before a paste my long blob of code, I want to mention a few things:

  • It's heavily based on the original drag sorting example.
  • Luckily React DnD has perfect TypeScript support as it's written in TypeScript.
  • The code below is a drop in replacement for Ant designs <table>.
  • It's generic.
  • It uses functional components and hooks.
  • There is a onDragSort, which gets called back every time some drag sort happened. The parameter includes the sorted data.

First install the packages react-dnd and react-dnd-html5-backend. Add this to your DragSortingTable.tsx:

import * as React from 'react';import Table, { TableProps, TableComponents } from 'antd/lib/table';import { DndProvider, DropTarget, DragSource, DragElementWrapper, DropTargetSpec } from 'react-dnd';import HTML5Backend from 'react-dnd-html5-backend';let dragingIndex = -1;interface BodyRowProps {  isOver: boolean;  connectDragSource: DragElementWrapper<{}>;  connectDropTarget: DragElementWrapper<{}>;  style?: React.CSSProperties;  index: number;  className?: string;}const BodyRow: React.FC<BodyRowProps> = props => {  const style = { ...props.style, cursor: 'move' };  let { className } = props;  if (props.isOver) {    if (props.index > dragingIndex) {      className += ' drop-over-downward';    }    if (props.index < dragingIndex) {      className += ' drop-over-upward';    }  }  return props.connectDragSource(    props.connectDropTarget(      <tr className={className} style={style}>        {props.children}      </tr>    )  );};const rowSource = {  beginDrag(props) {    dragingIndex = props.index;    return {      index: props.index    };  }};interface DropProps {  index: number;  moveRow: (dragIndex: number, hoverIndex: number) => void;}const rowTarget: DropTargetSpec<DropProps> = {  drop(props, monitor) {    const dragIndex = monitor.getItem().index;    const hoverIndex = props.index;    // Don't replace items with themselves    if (dragIndex === hoverIndex) {      return;    }    // Time to actually perform the action    props.moveRow(dragIndex, hoverIndex);    // Note: we're mutating the monitor item here!    // Generally it's better to avoid mutations,    // but it's good here for the sake of performance    // to avoid expensive index searches.    monitor.getItem().index = hoverIndex;  }};const DragableBodyRow = DropTarget<DropProps>('row', rowTarget, (connect, monitor) => ({  connectDropTarget: connect.dropTarget(),  isOver: monitor.isOver()}))(  DragSource('row', rowSource, connect => ({    connectDragSource: connect.dragSource()  }))(BodyRow));interface DragSortingTableProps<T> extends TableProps<T> {  onDragSort?: (sortedData: T[]) => void;}type extractTableType<T> = T extends DragSortingTableProps<infer T> ? T : never;export const DragSortingTable: <T>(  props: DragSortingTableProps<T>) => React.ReactElement<DragSortingTableProps<T>> = props => {  const [dataSource, setDataSource] = React.useState(props.dataSource);  React.useEffect(() => {    setDataSource(props.dataSource);  }, [props.dataSource]);  const components: TableComponents = {    body: {      row: DragableBodyRow    }  };  const moveRow: DropProps['moveRow'] = (dragIndex, hoverIndex) => {    const dragRow = dataSource[dragIndex];    const remaining = dataSource.filter(i => i !== dragRow);    const sorted = [...remaining.slice(0, hoverIndex), dragRow, ...remaining.slice(hoverIndex)];    setDataSource(sorted);    if (props.onDragSort) {      props.onDragSort(sorted);    }  };  const tableProps: TableProps<extractTableType<typeof props>> = {    ...props,    className: props.className ? props.className + ' drag-sorting-table' : 'drag-sorting-table',    components,    dataSource,    onRow: (_record, index) => ({ index, moveRow })  };  return (    <DndProvider backend={HTML5Backend}>      <Table {...tableProps}>{props.children}</Table>    </DndProvider>  );};

Add the following styles to your antd.less overwrites:

.drag-sorting-table tr.drop-over-downward td {  border-bottom: 2px dashed @primary-color;}.drag-sorting-table tr.drop-over-upward td {  border-top: 2px dashed @primary-color;}

Use your new drag sorting table as follows:

<DragSortingTable<Users>      dataSource={props.users}      rowKey='id'    >      <Column        title='Title'        dataIndex='id'        key='id'      /></DragSortingTable>


have you added CSS of antd sortable table to show the dragging effect?

#components-table-demo-drag-sorting tr.drop-over-downward td {  border-bottom: 2px dashed #1890ff;}#components-table-demo-drag-sorting tr.drop-over-upward td {  border-top: 2px dashed #1890ff;}

here is the sandbox link