Skip to content

Expose drag preview ref and allow opting out of the custom drag preview #329

@TrevorBurnham

Description

@TrevorBurnham

Background

Originally proposed in #133 (now closed).

Today, Tree always mounts DragPreviewContainer, which renders either the user's renderDragPreview or the built-in DefaultDragPreview. Inside useDragHook, we unconditionally call preview(getEmptyImage()) to suppress the browser's native drag image so our overlay can take its place.

This has two downsides:

  1. No way to use the browser's native drag preview. Consumers who want the plain, GPU-accelerated browser drag image (with no custom overlay) can't get it — we always hide the native image.
  2. Stutter on Chrome/macOS. When the custom preview moves outside the drop target, Chrome delays drop events by ~250ms, causing a visible stutter. Using the native preview avoids this.

Additionally, consumers currently have no way to attach a drag preview element separately from the drag handle. React-dnd's useDrag exposes both a ref (drag source) and a preview connector, but only dragHandle is forwarded to NodeRenderer.

Proposed API

Two independent additions:

1. previewHandle on NodeRendererProps

Additive, non-breaking. Forwards the preview connector from useDrag so consumers can attach it to a specific DOM node (e.g. a wrapper element that excludes toolbars/badges).

type NodeRendererProps<T> = {
  // ...existing fields
  dragHandle?: (el: HTMLDivElement | null) => void;
  previewHandle?: (el: HTMLDivElement | null) => void;
};

2. Opt-out for the custom drag preview

Let consumers say "use the browser's native preview, don't render DragPreviewContainer, don't call getEmptyImage()." Open question on the exact shape — options:

  • <Tree disableDragPreview /> — boolean prop, simple.
  • <Tree renderDragPreview={null} /> — reuse the existing prop; undefined keeps today's default, null opts out.

Either preserves backward compatibility: existing users with no renderDragPreview continue to see DefaultDragPreview.

Out of scope / follow-up

getEmptyImage() comes from react-dnd-html5-backend. Now that Tree accepts a custom dndBackend (#326), calling getEmptyImage() with a non-HTML5 backend is a latent bug. Worth fixing alongside this, but not required.

Prior art

PR #133 implemented (1) and a version of (2), but changed the default behavior (flipped the fallback from DefaultDragPreview to native), which would be a breaking visual change for every consumer who hasn't passed renderDragPreview. Closing that PR in favor of this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions