Simple Sortable List

Sortable list in React

If you're looking to incorporate draggable and sortable components into your application, dnd-kit is the preferred React library. It is well-suited for developing applications similar to JIRA and Trello.

Create a basic sortable list by following the steps outlined below.

Create react app and install following packages using npm or yarn

1 npm install @dnd-kit/core @dnd-kit/sortable dnd-kit/utilities

Create a main SortableList component

This component will need following things:

  • DndContext : This is needed to using drag and drop functionality in an application. This establishes the drag-and-drop context for the specified components. It accepts various props to configure the behavior of the drag-and-drop context. Some common props include onDragEnd and collisionDetection.
    • onDragEnd : This is utilized to manage the conclusion of the drag operation. Within this context, we can define the logic for sorting items.
    • collisionDetection : This is used to specify how collision detection is performed, enabling dnd-kit to identify which element should be sorted with. Here we will be providing closestCorners algorithm. This algorithm calculates the distance between the dragged item and the colliding item, helping determine which item should be replaced during the sorting process.

For this component, we will supply dummy data for experimentation. Additionally, we will furnish the SortableContainer component, which will be created in the next step.

The SortableList component will look like this.

1import "./SortableList.css";
2import { closestCorners, DndContext } from "@dnd-kit/core";
3import { useState } from "react";
4import SortableContainer from "./SortableContainer.js";
5import { arrayMove } from "@dnd-kit/sortable";
6
7function SortableList() {
8
9 // Dummy data
10 const [tasks, setTasks] = useState([
11 { id: 1, text: "Visit the post office" },
12 { id: 2, text: "Read a chapter of a book" },
13 { id: 3, text: "Exercise for 30 minutes" },
14 { id: 4, text: "Schedule a team meeting" },
15 { id: 5, text: "Pick up prescription from the pharmacy" }
16 ]);
17
18 // Sorting logic to update items in a state
19 const onDragEndHandler = (event) => {
20
21 // Event object provides two properties
22 // active , associated with the item that is currently being dragged
23 // over, associated with the item that is being dragged over.
24 const { active, over } = event;
25 if (active.id === over.id) return;
26
27 setTasks((previous) =>
28 {
29 // To find out indexes of items to udpate state
30 const oldPosition = previous.findIndex((x) => x.id === active.id);
31 const newPosition = previous.findIndex((x) => x.id === over.id);
32
33 // Method provided in @dnd-kit/sortable to sort the items based on indexes
34 return arrayMove(previous, oldPosition, newPosition);
35 })
36 };
37
38 return (
39 <div className="main">
40 <h3 className="heading">Sortable List</h3>
41 <DndContext id="sortable-list"
42 collisionDetection={closestCorners}
43 onDragEnd={onDragEndHandler}
44 >
45 <SortableContainer tasks={tasks}></SortableContainer>
46 </DndContext>
47 </div>
48 );
49}
50
51export default SortableList;

and this is its css

1.main {
2 text-align: center;
3}
4
5.heading {
6 width: 50%;
7 margin: 50px auto;
8 max-width: 500px;
9 background: #c6daf7;
10 padding: 10px;
11 border-radius: 10px;
12}

Create SortableContainer component

This component needs following things:

  • SortableContext : This establishes a sorting context for the designated component, necessary for utilizing sorting functionality. For this we will provided items and sorting strategy.
    • item : We must furnish the items that are intended to be sortable, and the SortableContext ensures handling of the sorting logic.
    • strategy : There are four distinct strategies that can be supplied to this, namely rectSortingStrategy, verticalListSortingStrategy, horizontalListSortingStrategy, and rectSwappingStrategy. In this case, we will utilize the verticalListSortingStrategy since we are constructing a sortable list in a vertical orientation.

Also, we will provide the SortableItem component, which will be created in the next step.

The SortableContainer component will look like this.

1import {
2 SortableContext,
3 verticalListSortingStrategy,
4} from "@dnd-kit/sortable";
5import "./SortableContainer.css";
6import SortableItem from "./SortableItem";
7const SortableContainer = ({ tasks }) => {
8 return (
9 <div className="container">
10 <SortableContext items={tasks} strategy={verticalListSortingStrategy}>
11 {tasks.map((task) => (
12 <SortableItem id={task.id} text={task.text} key={task.id} />
13 ))}
14 </SortableContext>
15 </div>
16 );
17};
18export default SortableContainer;

and its css

1.container {
2 background: #c6daf7;
3 max-width: 500px;
4 display: flex;
5 flex-direction: column;
6 width: 80%;
7 border-radius: 10px;
8 margin: auto;
9}

Creating SortableItem component

For SortableItem we will be using useSortable hook.

  • useSortable : This hooks provides few attribues for dragable elements which are namely attributes, listeners, setNodeRef, transform and transition. Out of this, transform and transition properties are related to css style and we will provide them via style object. For the useSortable hook to operate correctly, it requires the association of the setNodeRef property with the HTML element which will be a sortable element.

The SortableItem component will look like this

1import { useSortable } from "@dnd-kit/sortable";
2import "./SortableItem.css";
3import { CSS } from "@dnd-kit/utilities";
4const SortableItem = ({ id, text }) => {
5 // using useSortable hook
6 const { attributes, listeners, setNodeRef, transform, transition } =
7 useSortable({ id });
8
9 const style = {
10 transition,
11
12 // using utility method from '@dnd-kit/utilities' to convert transform
13 // attribute from useSortable hook into css style property
14 transform: CSS.Transform.toString(transform),
15 };
16
17 return (
18 <div
19 ref={setNodeRef}
20 {...attributes}
21 {...listeners}
22 style={style}
23 className="item"
24 >
25 {text}
26 </div>
27 );
28};
29export default SortableItem;

and its css

1.item {
2 background: #2a5ea4;
3 margin: 7px;
4 border-radius: 10px;
5 padding: 10px;
6 color: white;
7 touch-action: none;
8}

Sensors in DndContext

The DndContext comes with a Pointer Sensor by default. If we wish to enable our component for keyboard and touch devices, we should supply the DndContext for our component, along with associated sensors, using the useSensors and useSensor hooks.

Final implementation of SortableList component will look like this

1import "./SortableList.css";
2import {
3 closestCorners,
4 DndContext,
5 KeyboardSensor,
6 PointerSensor,
7 TouchSensor,
8 useSensor,
9 useSensors,
10} from "@dnd-kit/core";
11import { useState } from "react";
12import SortableContainer from "./SortableContainer.js";
13import { arrayMove } from "@dnd-kit/sortable";
14
15function SortableList() {
16
17 // Dummy data
18 const [tasks, setTasks] = useState([
19 { id: 1, text: "Visit the post office" },
20 { id: 2, text: "Read a chapter of a book" },
21 { id: 3, text: "Exercise for 30 minutes" },
22 { id: 4, text: "Schedule a team meeting" },
23 { id: 5, text: "Pick up prescription from the pharmacy" }
24 ]);
25
26 // Sorting logic to update items in a state
27 const onDragEndHandler = (event) => {
28
29 // Event object provides two properties
30 // active , associated with the item that is currently being dragged
31 // over, associated with the item that is being dragged over.
32 const { active, over } = event;
33 if (active.id === over.id) return;
34
35 setTasks((previous) =>
36 {
37 // To find out indexes of items to udpate state
38 const oldPosition = previous.findIndex((x) => x.id === active.id);
39 const newPosition = previous.findIndex((x) => x.id === over.id);
40
41 // Method provided in @dnd-kit/sortable to sort the items based on indexes
42 return arrayMove(previous, oldPosition, newPosition);
43 })
44 };
45
46 // Providing necessary sensors
47 const sensors = useSensors(
48 useSensor(PointerSensor),
49 useSensor(TouchSensor),
50 useSensor(KeyboardSensor, {
51 coordinateGetter: sortableKeyboardCoordinates,
52 })
53 );
54
55 return (
56 <div className="main">
57 <h3 className="heading">Sortable List</h3>
58 <DndContext
59 collisionDetection={closestCorners}
60 onDragEnd={onDragEndHandler}
61 sensors={sensors}
62 >
63 <SortableContainer tasks={tasks}></SortableContainer>
64 </DndContext>
65 </div>
66 );
67}
68
69export default SortableList;

Demo

Sortable List

Visit the post office
Read a chapter of a book
Exercise for 30 minutes
Schedule a team meeting
Pick up prescription from the pharmacy

Conclusion

In this, we've explored creating a sortable list with a single droppable target. In future blog posts, we'll delve into building a drag-and-drop application with multiple droppable targets.