React — Transfer between two lists
Reorder items within one list or transfer them to another. Both areas share one scope and group="tasks", with unique areaId values. Item IDs remain stable during transfer.
Try it
Section titled “Try it”Drag the left handle into the empty list on the right. The done count increases and the item leaves the first list. Reset returns all items to their original order. Transfer in this example uses pointer interaction.
import { useState } from 'react';import { SortableRoot, SortableArea } from 'comins-sortable/react';
type Item = { id: string; label: string };export default function ReactTransfer({ lang = 'ko' }: { lang?: string }) { const ko = lang === 'ko'; const initial = [{ id: 'plan', label: ko ? '문서 설계' : 'Design the docs' }, { id: 'build', label: ko ? '예제 구현' : 'Build an example' }, { id: 'review', label: ko ? '코드 검토' : 'Review the code' }]; const [todo, setTodo] = useState<Item[]>(initial); const [done, setDone] = useState<Item[]>([]); return <div className="demo" data-demo="sortable-react"> <div className="demo-toolbar"><span>{ko ? '손잡이를 잡고 다른 목록으로 이동하세요.' : 'Drag a handle into the other list.'}</span><button onClick={() => { setTodo(initial); setDone([]); }}>{ko ? '초기화' : 'Reset'}</button></div> <SortableRoot<Item>> <div className="transfer-board"> {[{ id: 'todo', title: ko ? '진행 예정' : 'To do', items: todo, update: setTodo }, { id: 'done', title: ko ? '완료' : 'Done', items: done, update: setDone }].map(list => <section key={list.id} className="transfer-column"> <div className="list-heading">{list.title}<span>{list.items.length}</span></div> <SortableArea<Item> areaId={list.id} group="tasks" items={list.items} itemKey="id" handle=".task-handle" onItemsChange={next => list.update([...next])} areaProps={{ className: 'task-list', 'aria-label': list.title }}> {item => <div className="task" data-task={item.id}><button className="task-handle" aria-label={`${ko ? '드래그' : 'Drag'}: ${item.label}`}>⠿</button><span>{item.label}</span></div>} </SortableArea> </section>)} </div> </SortableRoot> <p role="status" className="demo-status">{ko ? '완료' : 'Done'}: {done.length} / 3</p> </div>;}React state contract
Section titled “React state contract”SortableRoot owns the shared scope. Connect items, itemKey, and onItemsChange on each SortableArea. Commit each list array from its callback during transfer. children(item, index) is a render prop returning a React element. This contract is distinct from the same-named Vue components.
Handles use touch-action: none and user-select: none to avoid competing touch scroll and text selection during a drag.
Search terms
Section titled “Search terms”Transfer, move between lists, drag and drop, reorder, shared scope.