import React, {forwardRef, useLayoutEffect, useRef, useState} from 'react';
import {DragDropProvider} from '@dnd-kit/react';
import {useSortable} from '@dnd-kit/react/sortable';
import {move} from '@dnd-kit/helpers';
import {useWindowVirtualizer} from '@tanstack/react-virtual';
import {Feedback} from '@dnd-kit/dom';
interface SortableProps {
id: string | number;
index: number;
}
const Sortable = forwardRef<Element, SortableProps>(
function Sortable({id, index}, ref) {
const [element, setElement] = useState<Element | null>(null);
const handleRef = useRef<HTMLButtonElement | null>(null);
const {isDragging} = useSortable({
id,
index,
element,
plugins: [Feedback.configure({feedback: 'clone'})],
handle: handleRef,
});
return (
<div
ref={setElement}
data-index={index}
style={{
padding: '16px',
margin: '8px 0',
backgroundColor: isDragging ? '#f0f0f0' : 'white',
border: '1px solid #ddd',
borderRadius: '6px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<span>Item {id}</span>
<button
ref={handleRef}
style={{
cursor: 'grab',
border: 'none',
background: 'transparent',
fontSize: '18px',
}}
>
⋮⋮
</button>
</div>
);
}
);
export function VirtualizedListExample() {
// Create 1000 items
const [items, setItems] = useState(() =>
Array.from({length: 1000}, (_, i) => i + 1)
);
const snapshot = useRef(structuredClone(items));
const parentRef = useRef<HTMLDivElement>(null);
const parentOffsetRef = useRef(0);
// Configure virtualizer
const virtualizer = useWindowVirtualizer({
count: items.length,
estimateSize: () => 72, // Estimated row height
scrollMargin: parentOffsetRef.current,
getItemKey: (index) => items[index], // Use item ID as key
});
const virtualItems = virtualizer.getVirtualItems();
useLayoutEffect(() => {
parentOffsetRef.current = parentRef.current?.offsetTop ?? 0;
}, []);
return (
<DragDropProvider
onDragStart={() => {
snapshot.current = structuredClone(items);
}}
onDragOver={(event) => {
setItems((items) => move(items, event));
}}
onDragEnd={(event) => {
if (event.canceled) {
setItems(snapshot.current);
}
}}
>
<div ref={parentRef}>
<div
style={{
height: virtualizer.getTotalSize(),
width: '100%',
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
flexDirection: 'column',
padding: 20,
alignItems: 'center',
gap: 20,
transform: `translateY(${
virtualItems[0]?.start - virtualizer.options.scrollMargin
}px)`,
}}
>
{virtualItems.map(({key, index}) => (
<Sortable
ref={virtualizer.measureElement}
key={key}
id={items[index]}
index={index}
/>
))}
</div>
</div>
</div>
</DragDropProvider>
);
}