Documentation Index
Fetch the complete documentation index at: https://mintlify.com/clauderic/dnd-kit/llms.txt
Use this file to discover all available pages before exploring further.
Dnd-kit is designed for high performance, but large lists and complex interfaces require optimization strategies. Learn how to maintain smooth 60fps drag operations.
Memoize Sortable Components
Use memo to prevent unnecessary re-renders:
import {memo} from 'react';
const SortableItem = memo(function SortableItem({id, index}) {
const [element, setElement] = useState(null);
const {isDragging} = useSortable({id, index, element});
return (
<div
ref={setElement}
style={{opacity: isDragging ? 0.5 : 1}}
>
Item {id}
</div>
);
});
Optimize State Updates
Batch state updates and avoid inline object creation:
// Bad: Creates new object on every render
function SortableItem({id, index}) {
const {isDragging} = useSortable({
id,
index,
element,
data: {id, timestamp: Date.now()}, // New object every render!
});
}
// Good: Stable data reference
function SortableItem({id, index}) {
const data = useMemo(() => ({
id,
category: 'items',
}), [id]);
const {isDragging} = useSortable({id, index, element, data});
}
Stable Callback References
Use useCallback for event handlers:
function App() {
const [items, setItems] = useState([...]);
const handleDragEnd = useCallback((event) => {
if (event.canceled) return;
setItems((items) => move(items, event));
}, []); // Stable reference
return (
<DragDropProvider onDragEnd={handleDragEnd}>
{items.map((id, index) => (
<SortableItem key={id} id={id} index={index} />
))}
</DragDropProvider>
);
}
Virtualization for Large Lists
For lists with hundreds or thousands of items, use virtualization:
import {useVirtualizer} from '@tanstack/react-virtual';
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
function VirtualizedSortableList() {
const [items, setItems] = useState(
Array.from({length: 10000}, (_, i) => i)
);
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
});
return (
<DragDropProvider
onDragEnd={(event) => setItems((items) => move(items, event))}
>
<div ref={parentRef} style={{height: '600px', overflow: 'auto'}}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => {
const id = items[virtualItem.index];
return (
<VirtualSortableItem
key={id}
id={id}
index={virtualItem.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
/>
);
})}
</div>
</div>
</DragDropProvider>
);
}
const VirtualSortableItem = memo(function VirtualSortableItem({
id,
index,
style,
}) {
const [element, setElement] = useState(null);
const {isDragging} = useSortable({id, index, element});
return (
<div
ref={setElement}
style={{
...style,
opacity: isDragging ? 0.5 : 1,
height: 50,
padding: 12,
border: '1px solid #ddd',
}}
>
Item {id}
</div>
);
});
Virtualization works best with fixed-height items. Variable heights require additional complexity and may impact performance.
Optimizing Transitions
Disable Transitions for Large Lists
For lists with 100+ items, consider disabling transitions:
function SortableItem({id, index}) {
const {isDragging} = useSortable({
id,
index,
element,
transition: null, // No animation for better performance
});
}
Conditional Transitions
Only animate items near the drag operation:
function SortableItem({id, index, activeIndex}) {
const [element, setElement] = useState(null);
// Only transition if within 5 positions of active item
const shouldTransition = Math.abs(index - activeIndex) <= 5;
const {isDragging} = useSortable({
id,
index,
element,
transition: shouldTransition
? {duration: 250, easing: 'ease-out'}
: null,
});
}
Reduce Transition Duration
Shorter transitions improve perceived performance:
const {isDragging} = useSortable({
id,
index,
element,
transition: {
duration: 150, // Faster = snappier feel
easing: 'ease-out',
},
});
Sensor Configuration
Optimize Pointer Sensor
Reduce activation constraints for faster response:
import {PointerSensor} from '@dnd-kit/dom/sensors/pointer';
const optimizedPointer = PointerSensor.configure({
activationConstraints: (event) => {
// No constraints for mouse = instant activation
if (event.pointerType === 'mouse') {
return undefined;
}
// Minimal delay for touch
return [
new PointerActivationConstraints.Delay({value: 100, tolerance: 5}),
];
},
});
<DragDropProvider sensors={[optimizedPointer]}>
{/* Your content */}
</DragDropProvider>
Throttle Move Events
For complex collision detection, throttle move updates:
import {throttle} from 'lodash-es';
function App() {
const [items, setItems] = useState([...]);
// Throttle drag move events to every 16ms (60fps)
const handleDragMove = useMemo(
() => throttle((event) => {
// Custom move handling
}, 16),
[]
);
return (
<DragDropProvider
onDragMove={handleDragMove}
onDragEnd={(e) => setItems(move(items, e))}
>
{/* Items */}
</DragDropProvider>
);
}
Collision Detection Optimization
Use Efficient Collision Detectors
Choose the right collision detector for your use case:
import {
closestCenter, // Fast, good for sortables
closestCorners, // More precise, slightly slower
pointerWithin, // Very fast, pointer-based
rectIntersection, // Precise, slower for many items
} from '@dnd-kit/collision';
function SortableItem({id, index}) {
const {isDragging} = useSortable({
id,
index,
element,
collisionDetector: closestCenter, // Fast for most cases
});
}
Custom Collision Priority
Optimize collision detection order:
const {isDragging} = useSortable({
id,
index,
element,
collisionPriority: index, // Check items in index order
});
Render Optimization
Avoid Layout Thrashing
Batch DOM reads and writes:
// Bad: Read-write-read-write pattern
items.forEach(item => {
const height = item.offsetHeight; // Read
item.style.top = `${height}px`; // Write
});
// Good: Batch reads, then writes
const heights = items.map(item => item.offsetHeight);
items.forEach((item, i) => {
item.style.top = `${heights[i]}px`;
});
Dnd-kit handles this automatically, but be aware when adding custom code.
Use CSS Containment
Help the browser optimize rendering:
.sortable-item {
contain: layout style paint;
}
.sortable-container {
contain: layout;
}
will-change Hint
For frequently animated elements:
.sortable-item[data-dragging="true"] {
will-change: transform;
}
/* Remove when not dragging to save memory */
.sortable-item:not([data-dragging="true"]) {
will-change: auto;
}
Use will-change sparingly. Overuse can actually hurt performance by consuming too much memory.
Reduce Re-renders
// Bad: Recreates content on every render
function SortableItem({id, index}) {
const {isDragging} = useSortable({id, index, element});
return (
<div ref={setElement}>
<ExpensiveComponent data={complexData} /> {/* Re-renders often */}
</div>
);
}
// Good: Memoize expensive content
const ItemContent = memo(function ItemContent({data}) {
return <ExpensiveComponent data={data} />;
});
function SortableItem({id, index, data}) {
const {isDragging} = useSortable({id, index, element});
return (
<div ref={setElement} style={{opacity: isDragging ? 0.5 : 1}}>
<ItemContent data={data} />
</div>
);
}
Selective Re-rendering
Only subscribe to necessary state:
// Bad: Re-renders on any drag state change
function SortableItem({id, index}) {
const {sortable} = useSortable({id, index, element});
// sortable object changes frequently
return <div>{id}</div>;
}
// Good: Only re-render when isDragging changes
function SortableItem({id, index}) {
const {isDragging} = useSortable({id, index, element});
// Only extracts isDragging boolean
return <div style={{opacity: isDragging ? 0.5 : 1}}>{id}</div>;
}
Profiling and Debugging
import {Profiler} from 'react';
function App() {
const onRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
};
return (
<Profiler id="SortableList" onRender={onRenderCallback}>
<DragDropProvider>
{/* Your sortable list */}
</DragDropProvider>
</Profiler>
);
}
Monitor Frame Rate
function useFrameRate() {
useEffect(() => {
let lastTime = performance.now();
let frames = 0;
function measureFPS() {
const currentTime = performance.now();
frames++;
if (currentTime >= lastTime + 1000) {
console.log(`FPS: ${frames}`);
frames = 0;
lastTime = currentTime;
}
requestAnimationFrame(measureFPS);
}
const rafId = requestAnimationFrame(measureFPS);
return () => cancelAnimationFrame(rafId);
}, []);
}
Production Optimizations
Code Splitting
Lazy load drag and drop functionality:
import {lazy, Suspense} from 'react';
const SortableList = lazy(() => import('./SortableList'));
function App() {
const [showSortable, setShowSortable] = useState(false);
return (
<>
<button onClick={() => setShowSortable(true)}>
Enable Sorting
</button>
{showSortable && (
<Suspense fallback={<div>Loading...</div>}>
<SortableList />
</Suspense>
)}
</>
);
}
Tree Shaking
Import only what you need:
// Good: Import specific functions
import {useSortable} from '@dnd-kit/react/sortable';
import {move} from '@dnd-kit/helpers';
// Avoid: Importing entire packages
import * as DndKit from '@dnd-kit/react';
Memoize Components
Use memo on all sortable items
Optimize Callbacks
Use useCallback for event handlers
Consider Virtualization
For lists with 100+ items, implement virtualization
Tune Transitions
Adjust or disable animations for large lists
Profile in Production
Test performance with production builds and realistic data
Monitor Bundle Size
Use tools like webpack-bundle-analyzer to track dependencies
Avoid These Mistakes:
- Creating new objects/arrays in render
- Using array index as
key when items can be added/removed
- Inline styles that create new objects on every render
- Not memoizing expensive computations
- Enabling
idle transitions on large lists
- Using complex collision detectors unnecessarily
Next Steps
Measure Baseline Performance
Profile your current implementation to identify bottlenecks
Apply Optimizations Incrementally
Start with the highest-impact optimizations first
Test on Real Devices
Verify performance on mobile devices and low-end hardware