react-sortablejs

React bindings for SortableJS, giving drag-and-drop list reordering as a declarative, state-driven component.

Library
npm
v6.1.4
2,171stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
49/100Fair
Development Activity0
Maintenance32
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
52/100Fair
Architecture62
Code Quality40
Innovation45
Learning Curve60

react-sortablejs wraps the vanilla-JS SortableJS library in a single <ReactSortable> component that takes your list state and a setState-style callback and keeps them in sync with drag-and-drop reordering, without you touching the DOM directly. It supports both function and class components, works with any HTML tag (or a forwardRef custom component) via the tag prop, and exposes the full range of SortableJS’s own configuration options (group, animation, delay, filters, and more) as regular React props.

Beyond simple single-list sorting, it forwards SortableJS’s MultiDrag and Swap plugins so multi-select drag and swap-style reordering are available by mounting them once via Sortable.mount(), and it handles the trickier cross-list “clone” mode where an item dragged out of one list is duplicated back into its origin. Internally, every SortableJS DOM event (add/remove/update/select/deselect) is intercepted, its direct DOM mutation is undone, and the equivalent array splice is computed and handed back through your setList callback, so your component’s state stays the single source of truth.

What You Get

  • A single <ReactSortable> component that drives SortableJS from list/setList props instead of manual DOM wiring
  • Full pass-through of SortableJS’s native options (group, animation, delay, filter, handle, etc.) as React props
  • Built-in reconciliation of SortableJS’s DOM mutations back into your array state on add/remove/update/select/deselect
  • Support for the MultiDrag and Swap plugins, plus cross-list clone-on-drag behavior via a clone prop
  • Full TypeScript types for list items, props, and event handlers, published alongside the package

Common Use Cases

  • Reorderable to-do or task lists backed by React state
  • Kanban-style boards where cards can be dragged between multiple <ReactSortable> columns
  • Multi-select drag operations (moving several selected list items at once) via the MultiDrag plugin
  • Custom-tag or custom-component sortable lists (e.g. a <ul> or a design-system list component) via the tag prop

Under The Hood

Architecture The entire library is one class component, ReactSortable in src/react-sortable.tsx, that renders a ref-bound DOM element and mounts a raw Sortable.create() instance on it in componentDidMount. Props are translated into SortableJS Options via destructurePropsForOptions and a set of DOM/non-DOM handler wrappers built in makeOptions(), while a module-level store object tracks which ReactSortable instance is currently being dragged from, so cross-list operations (clone, multidrag) can reach into the origin list’s props. The trickiest part of the design lives in src/util.ts: SortableJS mutates the DOM directly on every drag event, so functions like removeNodes/insertNodes/handleStateAdd/handleStateRemove undo those DOM changes and translate them into the equivalent array splice, which is then handed back through the consumer’s setList prop. It’s a single flat module rather than a layered architecture, and the imperative-DOM-to-declarative-state bridge is workable but visibly fragile in places (module-level mutable store, several @todo comments about nesting and nested nodes).

Tech Stack Written in TypeScript against React’s legacy class-component API (targets react/react-dom >=16.9 as peer dependencies, no hooks used internally), with sortablejs and @types/sortablejs as peer dependencies it wraps. Runtime dependencies are minimal: classnames for conditional CSS classes and tiny-invariant for a handful of runtime assertions. The package is built with Parcel 2 (@parcel/packager-ts and @parcel/transformer-typescript-types generate the .d.ts output alongside the bundle), and the release process is fully automated via semantic-release, commitizen/commitlint, and husky git hooks enforcing conventional commits and a pre-push build.

Code Quality No test files exist anywhere in the repository, despite jest, ts-jest, and a jest.config.js being fully configured as dev dependencies — the testing setup is present but unused. Error handling is limited to a few tiny-invariant assertions guarding deprecated/misused props (e.g. the removed plugins prop, invalid clone usage in swap mode) rather than any broader typed error strategy. Naming is reasonably consistent, mirroring SortableJS’s own on-prefixed event names, but the source carries numerous @todo comments and several @ts-expect-error escapes for gaps in the upstream @types/sortablejs definitions. ESLint, Prettier, and lint-staged run on commit, and GitHub Actions workflows (build.yaml, release.yaml) build and publish the package, but with no tests to run, CI cannot catch behavioral regressions.

What Makes It Unique Its core technical job is reconciling an imperative, DOM-mutating drag library into React’s declarative rendering model: it captures every one of SortableJS’s DOM events, reverses the direct node manipulation SortableJS just performed, and recomputes the equivalent list splice to feed back into the consumer’s own state setter, including cross-list clone and multidrag/swap plugin scenarios. This ‘wrap a jQuery-era imperative library and reconcile it into React state’ pattern is a common approach for bridging vanilla-JS interactive libraries into React, competently executed here but not a novel technique specific to this project.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search