Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Comparative Analysis of Popular React Drag-and-Drop Sorting Libraries

Tech May 18 2

This article examines several popular libraries for implementing drag-and-drop functionality in React applications, focusing on their core concepts, usage, and compatibility with other UI components.

Core Libraries Overview

Four prominent libraries are discussed:

  1. React DnD

    • Core Concepts:
      • Backend: Handles browser differences, DOM events, and translates them into internal Redux actions. Supports HTML5 backend and custom implementations.
      • Item: A data object representing the dragged element (e.g., { cardId: 42 }).
      • Type: Enumerated constants defining draggable item types within the application.
      • Monitor: Stores and provides access to the drag-and-drop state (e.g., whether a drag is in progress, current item and type).
      • Connector: Links components to the Backend, enabling access to the DOM.
      • DragSource: A Higher-Order Component (HOC) making a component a draggable source.
      • DropTarget: An HOC making a component a drop target.
      • DragDropContext: Wraps the root component to provide the drag-and-drop context.
    • Demo: codesandbox.io/s/github/re…
  2. React Beautiful DnD

    • Core Concepts:
      • DragDropContext: Wraps the root component. Both Draggable and Droppable must be descendants.
      • Draggable: Wraps components to make them draggable.
      • Droppable: Wraps components to make them drop targets.
    • Demo: codesandbox.io/s/k260nyxq9v
  3. dnd-kit

    • Core Concepts:
      • DndContext: Wraps the root component, similar to DragDropContext.
      • Droppable: Makes components capable of receiving dropped items.
      • Draggable: Makes components capable of being dragged.
      • Sensors: Detect various input methods (pointer, mouse, touch, keyboard) to initiate, track, and end drag operations.
      • Modifiers: Dynamically alter sensor-detected coordinates. Use cases include constraining movement to an axis, within container boundaries, or applying friction.
    • Demo: chromatic.com/iframe.html…
  4. react-sortable-hoc

Ant Design Table Integration

Examples demonstrating integration with Ant Design's Table component:

Tree Component Compatibility

Mobile Device Compatibility

Infinite Scrolling Suppport

Implementing infinite scrolling with drag-and-drop typically involves virtual list techniques. Library support varies:

Feature Comparision Summary

  • React DnD
    • Documentation: Comprehensive.
    • GitHub Stars: 16.4k.
    • Activity: Actively maintained (updated within the last month).
    • Learning Curve: High.
    • Features: Moderate.
    • Mobile Support: Good.
    • Examples: Moderate.
    • Complexity: High due to numerous concepts and complex usage.
    • Architecture: Promotes component decoupling.
  • React Beautiful DnD
    • Documentation: Comprehensive.
    • GitHub Stars: 24.8k.
    • Activity: Actively maintained (updated within the last three months).
    • Learning Curve: High.
    • Ease of Use: Moderate.
    • Features: Rich.
    • Mobile Support: Excellent.
    • Examples: Abundant.
    • Focus: Specifically designed for vertical and horizontal lists, offering a higher-level abstraction than React DnD but with less broad functionality.
    • User Experience: Visually appealing, accessible, and provides a natural, physical feel to object movement.
    • Philosophy: Primarily supports drag-and-drop, not copy/clone operations.
  • dnd-kit
    • Documentation: Comprehensive.
    • GitHub Stars: 2.8k.
    • Activity: Actively maintained (updated within the last month).
    • Learning Curve: Moderate.
    • Ease of Use: Moderate.
    • Features: Moderate.
    • Mobile Support: Moderate.
    • Examples: Abundant.
    • Copy/Clone: Not explicitly observed.
  • react-sortable-hoc
    • Documentation: Limited.
    • GitHub Stars: 9.5k.
    • Activity: Actively maintained (updated within the last three months).
    • Learning Curve: Low.
    • Ease of Use: Low.
    • Features: Simple, primarily focused on sorting.
    • Mobile Support: Moderate.
    • Examples: Moderate.
    • Cross-Container Dragging: Not supported.
    • Copy/Clone: Not explicitly observed.

Recommendations:

  • For integrating with Ant Design's Table, react-sortable-hoc offers the simplest approach.
  • For infinite scrolling, while react-sortable-hoc has many examples, react-beautiful-dnd might be a better choice due to more extensive source code usage.
  • For tree dragging with basic requirements, Ant Design's built-in tree component suffices. For advanced needs, react-beautiful-dnd is recommended.
  • For mobile compatibility, consider react-sortable-hoc or react-beautiful-dnd.

Implementing a Basic Drag-and-Drop Component

HTML5 Drag and Drop API

To make an element draggable, set the draggable attribute:

<img draggable="true">

Key event handlers:

  • ondrag: Fires while dragging.
  • ondragend/ondragstart: Fires at the end and beginning of a drag operation.
  • ondragover: Fires when a dragged element is over a valid drop target.
  • ondragenter/ondragleave: Fires when a dragged element enters or leaves a drop target.
  • ondrop: Fires when the element is dropped on a target.

Data transfer is managed via event.dataTransfer:

  • setData(type, data): Adds data to the transfer. type is a custom string identifier, data is the payload.
  • getData(type): Retrieves data by its type.
  • clearData(): Clears all transferred data.
  • setDragImage(element, x, y): Customizes the drag preview image.
  • effectAllowed: Defines the allowed effects (e.g., copy, move, link).

Functional and Architectural Design

  1. Utilize React Hooks.
  2. Components: Drag (draggable item), Drop (drop target), DndContext (context provider).
  3. Support for mobile devices.
  4. Support for sorting.

Code Implementation

Drag Component Usage:

<Drag index={1} id='1'>
  <div>Draggable Content</div>
</Drag>

Drag Component Implemantation:

import { FC } from "react";

interface DragProps {
  index: number;
  id: string | number;
}

const Drag: FC<DragProps> = (props) => {
  const handleDragStart = (ev) => {
    // Transfer data
    ev.dataTransfer.setData("index", props.index.toString());
    ev.dataTransfer.setData("id", props.id.toString());
  };

  return (
    <div draggable onDragStart={handleDragStart}>
      {props.children}
    </div>
  );
};

export default Drag;

Drop Component Usage:

<Drop>
  <Drag index={1} id='1'>
    <div>Draggable Content</div>
  </Drag>
</Drop>

Drop Component Implementation:

import { FC, useContext } from "react";
import { Context } from "./DndContext";

const Drop: FC = (props) => {
  const { handleDragOver, handleDrop } = useContext(Context);

  const dragOverHandler = (ev) => {
    ev.preventDefault(); // Necessary to allow dropping
    if (handleDragOver) handleDragOver();
  };

  const dropHandler = (ev) => {
    // Retrieve data
    const oldIndexStr = ev.dataTransfer.getData("index");
    const oldIndex = parseInt(oldIndexStr, 10);

    // Basic calculation for new index based on clientY. 
    // This is a simplification; actual implementation might be more complex.
    const targetRect = ev.currentTarget.getBoundingClientRect();
    const mouseYRelativeToTarget = ev.clientY - targetRect.top;
    const itemHeight = 20; // Assume a fixed height for simplicity
    const newIndex = Math.floor(mouseYRelativeToTarget / itemHeight);

    if (!isNaN(oldIndex)) {
      if (handleDrop) handleDrop(oldIndex, newIndex);
    }
  };

  return (
    <div onDragOver={dragOverHandler} onDrop={dropHandler}>
      {props.children}
    </div>
  );
};

export default Drop;

DndContext Component Usage:

<DndContext
  onDropEnd={(oldIndex, newIndex) => {
    setData(arrayMove(data, oldIndex, newIndex));
  }}
  onDragOver={() => { /* Handle drag over if needed */ }}
>
  <Drop>
    {data.map((item, index) => (
      <Drag key={item.id} id={item.id} index={index}>
        <div className="item">{item.text}</div>
      </Drag>
    ))}
  </Drop>
</DndContext>

DndContext Component Implementation:

import { createContext, FC, ReactNode } from "react";

interface DndContextProps {
  onDropEnd: (oldIndex: number, newIndex: number) => void;
  onDragOver: () => void;
  children: ReactNode;
}

interface ContextValue {
  handleDragOver: () => void;
  handleDrop: (oldIndex: number, newIndex: number) => void;
}

export const Context = createContext<ContextValue>({} as ContextValue);

const DndContext: FC<DndContextProps> = (props) => {
  return (
    <Context.Provider
      value={{
        handleDragOver: props.onDragOver,
        handleDrop: props.onDropEnd,
      }}
    >
      {props.children}
    </Context.Provider>
  );
};

export default DndContext;

For mobile compatibility, libraries like react-touch-dnd (github.com/timruffles/…) can be used.

Areas for Improvement

  1. Accurate calculation of drop position.
  2. Real-time position updates during drag.
  3. Performance optimizations.
  4. Robust error handling.
  5. Drag-and-drop visual feedback styles.
  6. Directional dragging (X/Y axis).
  7. And other enhancements.

Live Demo

See the complete online example: codesandbox.io/embed/react…

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.