# An Easy to Use React DnD Sortable Component

## Target

![](https://img.content.cc/a/2022/04/02/13-36-00-655-e68fe36331dc415113a95175f7d19964-c41319.gif)

* Easy to use
* Less code

## Code

You can simply copy the following code to a `dnd-sortable.tsx` file, and use as a component.

```typescript
import React, { FC, ReactNode, useCallback, useRef, useState } from "react";
import { useDrag, useDrop } from "react-dnd";
import type { XYCoord, Identifier } from "dnd-core";
import update from "immutability-helper";

export interface NodeProps {
  id: any;
  index: number;
  node: ReactNode;
  moveNode: (dragIndex: number, hoverIndex: number) => void;
}

interface DragItem {
  index: number;
  id: string;
  type: string;
}

export const Node: FC<NodeProps> = ({ id, index, node, moveNode: moveNode }) => {
  const ref = useRef<HTMLDivElement>(null);
  const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
    accept: "item",
    collect(monitor) {
      return {
        handlerId: monitor.getHandlerId(),
      };
    },
    hover(item: DragItem, monitor) {
      if (!ref.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;

      // Don't replace items with themselves
      if (dragIndex === hoverIndex) {
        return;
      }

      // Determine rectangle on screen
      const hoverBoundingRect = ref.current?.getBoundingClientRect();

      // Get vertical middle
      const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;

      // Determine mouse position
      const clientOffset = monitor.getClientOffset();

      // Get pixels to the top
      const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;

      // Only perform the move when the mouse has crossed half of the items' height
      // When dragging downwards, only move when the cursor is below 50%
      // When dragging upwards, only move when the cursor is above 50%

      // Dragging downwards
      if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
        return;
      }

      // Dragging upwards
      if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
        return;
      }

      // Time to actually perform the action
      moveNode(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.
      item.index = hoverIndex;
    },
  });

  const [{ isDragging }, drag] = useDrag({
    type: "item",
    item: () => {
      return { id, index };
    },
    collect: (monitor: any) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  const opacity = isDragging ? 0.5 : 1;
  drag(drop(ref));
  return (
    <div ref={ref} style={{ opacity }} data-handler-id={handlerId}>
      {node}
    </div>
  );
};

export interface Item {
  id: number;
  node: ReactNode;
}

export default function DndSortable({ nodeList }: { nodeList: Array<ReactNode> }) {
 // Set id for every react node for react-dnd to use
  const [itemList, setItemList] = useState(nodeList.map((node, index) => ({ id: index, node: node })));

  const moveNode = useCallback((dragIndex: number, hoverIndex: number) => {
    setItemList((prevNodes: Item[]) =>
      update(prevNodes, {
        $splice: [
          [dragIndex, 1],
          [hoverIndex, 0, prevNodes[dragIndex] as Item],
        ],
      })
    );
  }, []);

  const renderNode = useCallback((id: number, index: number, node: ReactNode) => {
    return <Node key={id} index={index} id={id} node={node} moveNode={moveNode} />;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return <>{itemList.map((item, index) => renderNode(item.id, index, item.node))}</>;
}
```

## Usage

```typescript
const cards = useRef([
  {
    id: 1,
    text: "Write a cool JS library",
  },
  {
    id: 2,
    text: "Make it generic enough",
  },
  {
    id: 3,
    text: "Write README",
  },
  {
    id: 4,
    text: "Create some examples",
  },
  {
    id: 5,
    text: "Spam in Twitter and IRC to promote it (note that this element is taller than the others)",
  },
  {
    id: 6,
    text: "The origin text here is a symbol that is not ok",
  },
  {
    id: 7,
    text: "PROFIT",
  },
]);

...

<DndSortable
  nodeList={cards.current.map((card, i) => {
    return (
      <div key={i} className="dnd-sortable">
        {card.text}
      </div>
    );
  })}
/>
```

## Example

{% embed url="<https://codesandbox.io/s/bold-benz-srkg21>" %}

## Reference

1. The react-dnd official simple sortable example: [Sortable Simple](https://react-dnd.github.io/react-dnd/examples/sortable/simple)
2. [React DnD](https://github.com/react-dnd/react-dnd/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://a.b.cr/textbook/frontend/a-easy-to-use-react-dnd-sortable-component.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
