Arrastrar y Soltar (Drag & Drop)
Reglas
- Usa
@dnd-kit/core + @dnd-kit/sortable — una biblioteca moderna, accesible y táctil para arrastrar y soltar
- Listas ordenables: envuélvelas en
<SortableContext>, cada elemento usa el hook useSortable()
- Tableros Kanban:
<DndContext> con múltiples contenedores <SortableContext> — uno por columna
- Zonas de carga de archivos:
react-dropzone o los eventos nativos onDragOver/onDrop con e.dataTransfer.files
- Soporte táctil: dnd-kit maneja el táctil de forma nativa — usa
TouchSensor y PointerSensor con restricciones de activación
- Accesibilidad: dnd-kit proporciona arrastre y soltar por teclado listo para usar —
KeyboardSensor con sortableKeyboardCoordinates
- Reordenamiento optimista: actualiza el estado inmediatamente al soltar, persiste al servidor en segundo plano, revierte en caso de error
- Superposición de arrastre: usa
<DragOverlay> para una vista previa flotante del elemento arrastrado — mejor retroalimentación visual
Patrones
import { DndContext, closestCenter, DragOverlay } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
function SortableItem({ id, children }: { id: string; children: React.ReactNode }) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = { transform: CSS.Transform.toString(transform), transition };
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
{children}
</div>
);
}
function SortableList({ items }: { items: Item[] }) {
const [activeId, setActiveId] = useState<string | null>(null);
return (
<DndContext
collisionDetection={closestCenter}
onDragStart={({ active }) => setActiveId(active.id)}
onDragEnd={handleDragEnd}
>
<SortableContext items={items.map((i) => i.id)} strategy={verticalListSortingStrategy}>
{items.map((item) => (
<SortableItem key={item.id} id={item.id}>{item.name}</SortableItem>
))}
</SortableContext>
<DragOverlay>{activeId ? <ItemPreview id={activeId} /> : null}</DragOverlay>
</DndContext>
);
}
Evita
- Usar la API de arrastre HTML5 directamente — mal soporte táctil/móvil/accesibilidad
- Olvidar la accesibilidad por teclado — incluye siempre
KeyboardSensor
- Mutar el estado durante el arrastre — actualiza solo en
onDragEnd
- Omitir
DragOverlay — sin él, el elemento original desaparece durante el arrastre sin retroalimentación visual