The React binding wraps SnapSort’s core objects in React components. Use it when React owns the rendered arrays and your component state remains the source of truth for item order.

Install

npm install @snap-engine/snapsort @snap-engine/asset-base react react-dom

react, react-dom, and @snap-engine/asset-base are optional peers of the unified package and are required only when you import the React binding.

Import

import {
  Engine,
  Container,
  Ghost,
  Handle,
  Item,
} from "@snap-engine/snapsort/react";
import {
  reduceRenderTree,
  type ContainerCallbacks,
  type RenderTreeEvent,
} from "@snap-engine/snapsort";

Wrap the sortable tree in an Engine, then render the application’s mixed RenderTree.entries sequence. The root callbacks synchronously pass item moves and all three explicit ghost events through one reduceRenderTree state update. Each keyed entry renders as a Ghost, a recursive child Container, or a leaf Item:

The excerpt assumes tasks and setTasks come from the useState RenderTree setup, and TaskContainer is the recursive component shown on the Container page.

const applyTreeEvent = (event: RenderTreeEvent) => {
  setTasks((current) => reduceRenderTree(current, event));
};

const callbacks = {
  onItemMove: applyTreeEvent,
  onGhostInsert: applyTreeEvent,
  onGhostMove: applyTreeEvent,
  onGhostRemove: applyTreeEvent,
} satisfies ContainerCallbacks;

<Engine id="tasks">
  <Container itemId="tasks-root" config={{ callbacks }}>
    {tasks.entries.map((entry) =>
      entry.isGhost ? (
        <Ghost key={entry.itemId} ghost={entry.ghost} />
      ) : entry.childTree ? (
        <TaskContainer key={entry.itemId} entry={entry} />
      ) : (
        <Item key={entry.itemId} itemId={entry.itemId}>
          {entry.value.label}
        </Item>
      ),
    )}
  </Container>
</Engine>;

Here TaskContainer recursively renders entry.childTree with itemId={entry.itemId}. The root alone receives the structural callbacks; see the Container page for the complete state and recursive component.

The callback count reflects ownership rather than item/ghost symmetry. A drag produces one semantic onItemMove for its complete source-to-destination transition. SnapSort itself creates, relocates, and removes ghosts, so React must receive all three explicit ghost-presence callbacks; they happen to share the applyTreeEvent function above.

Do not use these callbacks as a general CRUD API. An application Add or Delete action updates tasks directly, and React mounting or unmounting the keyed Item synchronizes its core registration. Add onItemRemove: applyTreeEvent only when code invokes the core container.removeItem(id) command. RenderTreeEvent excludes ItemInsertEvent because a core insertion event cannot supply the application value for a new RenderEntry<T>.

Engine creates or accepts a core Engine, assigns it to the rendered root, installs collision handling, and provides it through React context. Use useSnapSortEngine() when a descendant needs the current core engine.

Components

  • Container — props, modes, callbacks, mutation timing, and the imperative ref API.
  • Item — props, identity, metadata, selection, refs, and drag handles.
  • Ghost — lifecycle state, rendering, geometry, customization, and DOM attributes.
  • DragSession — shared gesture state, lifecycle phases, and mutable controls.
  • Standard Callbacks — reusable destination policies.

Handle belongs inside an Item and limits where a drag can begin. Ghost renders any lifecycle GhostState from the mixed entry sequence and binds its engine-owned ghostItem to that element. It covers source and target spacers, insertion markers, and pointer previews. Do not render a ghost with Item, because a ghost is already attached to the core container by the drag lifecycle.

For an insertion marker, Ghost derives an exact container-local rectangle from the state’s zero-thickness gap. Its insertionMarker prop accepts the complete { thickness, startInset, endInset } presentation contract. Custom adapters use the framework-neutral insertionMarkerRect helper with the same options; none of those paint choices affect candidate ranking.

Every wrapper accepts standard div attributes and forwards them to its DOM element. style is merged after SnapSort’s structural flex/position defaults.

Core Types

Import callback events, configuration, metadata, and core object types from the framework-agnostic package:

import type { Engine as CoreEngine } from "@snap-engine/core";
import type {
  Container as SnapSortContainer,
  ContainerConfig,
  DragEndEvent,
  DragStartEvent,
  DropPriorityEvent,
  Item as SnapSortItem,
  ItemMoveEvent,
  ItemMetadata,
  ItemSwapEvent,
} from "@snap-engine/snapsort";

For application-state, ghost, and callback examples, see the Container reference.

Subpath Exports

Import pathExports
@snap-engine/snapsort/react/EngineEngine, context, and useSnapSortEngine
@snap-engine/snapsort/react/ContainerContainer components and context
@snap-engine/snapsort/react/GhostFramework-owned lifecycle ghost view
@snap-engine/snapsort/react/ItemItem components and context
@snap-engine/snapsort/react/HandleHandle