@hello-pangea/dnd
supports drag and drop within and between virtual lists. This lets you have fantastic performance with very large data sets. As a general rule, you will want to start using a virtual list when your list size is more than 500 items.
A "virtual list" is the name given to a windowing performance optimisation technique where only the visible list items are rendered. See Rendering large lists with react-window by Addy Osmani for more background on virtual lists
Diagram from Creating more efficient React views with windowing by Brain Vaughn
There are drawbacks with using virtual lists. They stem from the fact that with a virtual list not all of the page content is rendered into the DOM.
- Accessibility: screenreaders cannot read out all of the content of a page
- Findability: native find (meta + f) will not find things that are not rendered in the DOM.
@hello-pangea/dnd
is designed to work with existing virtual list solutions and does not have it's own virtual list abstraction. There is no official "virtual list" specification or implementation for the web. Different virtual list libraries achieve windowing through various techniques. So we cannot guarentee that @hello-pangea/dnd
will work with every virtual list library. We have created examples for react-window
and react-virtualized
which are the two most popular virtual list libraries for react
.
Please raise a pull request if you would like to add examples for other virtualization libraries! ❤
React Virtuoso comes with automatic item measurement out of the box.
@hello-pangea/dnd
does not provide its own virtual list abstraction so there is a bit of wiring that you will need to do in order to get going with existing virtual list solutions 🛠
Virtualisation libraries often have overscanning enabled by default
Most virtual list libraries support the concept of overscanning. Overscanning is where a small about of non-visible items are rendered near the boundary of the window. When a scroll occurs the overscanned item can be immediately moved into view and does not need to be created. Overscanning generally leads to a more fluid experience.
It is required that overscanning be enabled for @hello-pangea/dnd
to work correctly. If overscanning is not enabled, rfd
cannot tell if there are more items in the list when an item is in the last visual position. We require an overscanning value of one or more.
Virtual lists behave differently to regular lists. You will need to tell rfd
that your list is a virtual one.
<Droppable droppableId="droppable" mode="virtual">
{/*...*/}
</Droppable>
When using a virtual list the original dragging item can be unmounted during a drag if it becomes invisible. To get around this we require that you drag a clone of the dragging item. See our reparenting guide for more details.
<Droppable
droppableId="droppable"
mode="virtual"
renderClone={(provided, snapshot, rubric) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
Item id: {items[rubric.source.index].id}
</div>
)}
>
{/*...*/}
</Droppable>
👋 This is only required when you have multiple connected lists. This is not required when using a single list
Usually we require consumers to put a placeholder
(<Droppable /> | DroppableProvided | placeholder
) into the list so that we can insert space into a list as needing during a drag.
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{/* Usually needed. But not for virtual lists! */}
{provided.placeholder}
</div>
)}
</Droppable>
However, a placeholder
does not make sense in the context of a virtual list as the dimensions of the list is not based on collective size of the visual items, but rather is calculated based on things like itemCount
and itemSize
. (eg height
= itemSize
* itemCount
). For virtual lists, inserting our own node into it would not increase the size of the list. So we need you do insert the space for us!
A simple way to add extra space to a virtual list is to add a non-visible item to your list. It is important that this extra item is not a <Draggable />
.
// This example uses the `react-window` API
const Row = ({ data, index, style }: RowProps) => {
const item = data[index];
// We are rendering an extra item for the placeholder
if (!item) {
return null;
}
return (
<Draggable draggableId={item.id} index={index} key={item.id}>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
{/*...*/}
)}
</Draggable>
);
});
function render(provided: DroppableProvided, snapshot: DroppableStateSnapshot) {
// Add an extra item to our list to make space for a dragging item
// Usually the DroppableProvided.placeholder does this, but that won't
// work in a virtual list
const itemCount: number = snapshot.isUsingPlaceholder
? quotes.length + 1
: quotes.length;
return (
<List
height={500}
itemCount={itemCount}
itemSize={110}
width={300}
outerRef={provided.innerRef}
itemData={quotes}
>
{Row}
</List>
);
}