CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-dnd

Drag and Drop for React applications with hooks-based API and TypeScript support

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

utilities.mddocs/

Utilities

React DnD provides utility components and functions for advanced drag and drop scenarios and integration with the underlying drag and drop system.

Capabilities

DragPreviewImage

Utility component for rendering drag preview images with proper lifecycle management.

/**
 * A utility component for rendering a drag preview image
 * @param props - Preview image configuration
 * @returns React element (renders null, used for side effects)
 */
function DragPreviewImage(props: DragPreviewImageProps): React.ReactElement;

interface DragPreviewImageProps {
  /** Connector function from useDrag hook */
  connect: ConnectDragPreview;
  /** Image source URL */
  src: string;
}

Usage Example:

import React from "react";
import { useDrag, DragPreviewImage } from "react-dnd";

function DraggableWithImagePreview({ id, name, previewImageUrl }) {
  const [{ isDragging }, drag, preview] = useDrag({
    type: "item",
    item: { id, name },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  return (
    <div>
      {/* The DragPreviewImage component handles image loading and connection */}
      <DragPreviewImage connect={preview} src={previewImageUrl} />
      
      <div 
        ref={drag}
        style={{ opacity: isDragging ? 0.5 : 1 }}
      >
        {name}
      </div>
    </div>
  );
}

Advanced Usage with Dynamic Images:

import React, { useState, useEffect } from "react";
import { useDrag, DragPreviewImage } from "react-dnd";

function DynamicPreviewItem({ item }) {
  const [previewUrl, setPreviewUrl] = useState(null);

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

  // Generate or fetch preview image
  useEffect(() => {
    generatePreviewImage(item).then(setPreviewUrl);
  }, [item]);

  return (
    <div>
      {previewUrl && <DragPreviewImage connect={preview} src={previewUrl} />}
      
      <div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
        <ItemContent item={item} />
      </div>
    </div>
  );
}

async function generatePreviewImage(item) {
  // Generate canvas-based preview or fetch from API
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  
  canvas.width = 200;
  canvas.height = 100;
  
  ctx.fillStyle = "#f0f0f0";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#333";
  ctx.font = "16px Arial";
  ctx.fillText(item.name, 10, 50);
  
  return canvas.toDataURL();
}

useDragDropManager Hook

Low-level hook for accessing the DragDropManager instance directly for advanced operations.

/**
 * Hook to retrieve the DragDropManager from Context
 * @returns DragDropManager instance
 * @throws Error if used outside DndProvider
 */
function useDragDropManager(): DragDropManager;

Usage Examples:

import React, { useEffect, useState } from "react";
import { useDragDropManager } from "react-dnd";

function AdvancedDragMonitor() {
  const manager = useDragDropManager();
  const [dragState, setDragState] = useState({});

  useEffect(() => {
    const monitor = manager.getMonitor();
    
    // Subscribe to all drag and drop state changes
    const unsubscribe = monitor.subscribeToStateChange(() => {
      setDragState({
        isDragging: monitor.isDragging(),
        itemType: monitor.getItemType(),
        item: monitor.getItem(),
        dropResult: monitor.getDropResult(),
        clientOffset: monitor.getClientOffset(),
      });
    });

    return unsubscribe;
  }, [manager]);

  return (
    <div className="drag-monitor">
      <h3>Global Drag State</h3>
      <pre>{JSON.stringify(dragState, null, 2)}</pre>
    </div>
  );
}

Backend Management:

import React, { useEffect } from "react";
import { useDragDropManager } from "react-dnd";

function BackendController() {
  const manager = useDragDropManager();

  useEffect(() => {
    const backend = manager.getBackend();
    
    // Access backend-specific functionality
    if (backend.setup) {
      backend.setup();
    }

    // Custom backend configuration
    if (backend.addEventListener) {
      backend.addEventListener("dragstart", handleDragStart);
      backend.addEventListener("dragend", handleDragEnd);
    }

    return () => {
      if (backend.teardown) {
        backend.teardown();
      }
    };
  }, [manager]);

  const handleDragStart = (event) => {
    console.log("Backend drag start:", event);
  };

  const handleDragEnd = (event) => {
    console.log("Backend drag end:", event);
  };

  return <div>Backend Controller Active</div>;
}

Type Guards and Utilities

Utility functions for working with React DnD types and values.

/** Utility type for values that can be provided as instance or factory */
type FactoryOrInstance<T> = T | (() => T);

/** Factory function type for creating drag objects */
type DragObjectFactory<T> = (monitor: DragSourceMonitor<T>) => T | null;

/** Coordinate pair for pointer/element positions */
interface XYCoord {
  x: number;
  y: number;
}

/** Valid element types that can be connected for drag/drop */
type ConnectableElement = React.RefObject<any> | React.ReactElement | Element | null;

Utility Functions Example:

import React from "react";
import type { XYCoord, ConnectableElement } from "react-dnd";

// Helper function to calculate distance between coordinates
function calculateDistance(a: XYCoord, b: XYCoord): number {
  return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
}

// Helper function to check if element is connectable
function isConnectable(element: any): element is ConnectableElement {
  return element === null || 
         React.isValidElement(element) ||
         (element && typeof element === "object" && "current" in element) ||
         element instanceof Element;
}

// Helper to create factory functions
function createItemFactory<T>(baseItem: T) {
  return (monitor: DragSourceMonitor<T>): T => {
    return {
      ...baseItem,
      dragStartTime: Date.now(),
      initialOffset: monitor.getInitialClientOffset(),
    };
  };
}

function UtilityExample() {
  const itemFactory = createItemFactory({ id: "1", name: "Example" });
  
  const [{ isDragging }, drag] = useDrag({
    type: "item",
    item: itemFactory, // Use factory function
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  return <div ref={drag}>Draggable with factory</div>;
}

Integration Helpers

Custom Backend Integration

import React from "react";
import { DndProvider, useDragDropManager } from "react-dnd";

// Custom backend example
class CustomBackend {
  constructor(manager, context, options) {
    this.manager = manager;
    this.context = context;
    this.options = options;
  }

  setup() {
    // Initialize custom drag and drop handling
    this.context.addEventListener("mousedown", this.handleMouseDown);
    this.context.addEventListener("mouseup", this.handleMouseUp);
  }

  teardown() {
    // Clean up event listeners
    this.context.removeEventListener("mousedown", this.handleMouseDown);
    this.context.removeEventListener("mouseup", this.handleMouseUp);
  }

  handleMouseDown = (event) => {
    // Custom drag initiation logic
  };

  handleMouseUp = (event) => {
    // Custom drop handling logic
  };
}

function createCustomBackend(manager, context, options) {
  return new CustomBackend(manager, context, options);
}

function AppWithCustomBackend() {
  return (
    <DndProvider backend={createCustomBackend}>
      <MyDragDropComponents />
    </DndProvider>
  );
}

Testing Utilities

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import { DndProvider } from "react-dnd";
import { TestBackend } from "react-dnd-test-backend";

function createDndWrapper() {
  return function DndWrapper({ children }) {
    return (
      <DndProvider backend={TestBackend}>
        {children}
      </DndProvider>
    );
  };
}

// Example test utility
function simulateDragDrop(dragElement, dropElement) {
  fireEvent.dragStart(dragElement);
  fireEvent.dragEnter(dropElement);
  fireEvent.dragOver(dropElement);
  fireEvent.drop(dropElement);
  fireEvent.dragEnd(dragElement);
}

// Test example
test("drag and drop functionality", () => {
  const { getByTestId } = render(
    <MyDragDropComponent />,
    { wrapper: createDndWrapper() }
  );

  const dragElement = getByTestId("draggable");
  const dropElement = getByTestId("droppable");

  simulateDragDrop(dragElement, dropElement);

  // Assert expected behavior
});

Performance Optimization Helpers

import React, { useMemo, useCallback } from "react";
import { useDrag, useDrop } from "react-dnd";

// Memoized drag spec to prevent unnecessary re-renders
function OptimizedDraggable({ item, onDragEnd }) {
  const dragSpec = useMemo(() => ({
    type: "item",
    item,
    end: onDragEnd,
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  }), [item, onDragEnd]);

  const [{ isDragging }, drag] = useDrag(dragSpec);

  return (
    <div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
      {item.name}
    </div>
  );
}

// Optimized drop target with memoized handlers
function OptimizedDropTarget({ onDrop, acceptTypes }) {
  const handleDrop = useCallback((item, monitor) => {
    onDrop(item, monitor.getDropResult());
  }, [onDrop]);

  const dropSpec = useMemo(() => ({
    accept: acceptTypes,
    drop: handleDrop,
    collect: (monitor) => ({
      isOver: monitor.isOver(),
      canDrop: monitor.canDrop(),
    }),
  }), [acceptTypes, handleDrop]);

  const [{ isOver, canDrop }, drop] = useDrop(dropSpec);

  return (
    <div 
      ref={drop}
      style={{
        backgroundColor: isOver && canDrop ? "lightgreen" : "transparent",
      }}
    >
      Drop Zone
    </div>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-dnd

docs

context-provider.md

drag-layer.md

drag-sources.md

drop-targets.md

index.md

utilities.md

tile.json