I wanted to make an example getting started post as a reaction to an official vertical list example which I find to be overly complex.
What API surface does it provide? It provides a bunch of things:
DragDropContext
, this is a top-level container element.
Droppable
, which must envelop all draggables.
Draggable
, which appropriately enough must envelop an individual draggable item.
Each item expects its children prop to be a function, which it calls to get further elements to render. That enables it to pass the context information down the component tree. In some ways this is a good example of the issue I described earlier in the post FP & the Context Problem.
Luckily this library has a good set of types available in the DefinitelyTyped repository. You can find out most of the API from reading the TypeScript definitions. So a well-typed example is below.
import React, {useState} from 'react';
import {
DragDropContext, Droppable, Draggable,
DropResult, DroppableProvided, DroppableStateSnapshot, DraggableRubric,
DraggableStateSnapshot, DraggableProvided, ResponderProvided
} from 'react-beautiful-dnd';
interface AppProps {
};
function makeDraggableChildren(value: string) {
return (provided: DraggableProvided,
snapshot: DraggableStateSnapshot,
rubric: DraggableRubric) =>
<li ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}>{value}</li>;
}
function makeDroppableChildren(items: string[]) {
return (provided: DroppableProvided,
snapshot: DroppableStateSnapshot) =>
<ul ref={provided.innerRef}>
{items.map((value, i) => (
<Draggable draggableId={i.toString()}
index={i}
key={i}>
{makeDraggableChildren(value)}
</Draggable>
))}
{provided.placeholder}
</ul>;
}
export function DndDemo(props: AppProps) {
const [items, setItems] = useState(['fry', 'bender', 'leela']);
const onDragEnd = (result: DropResult, provided: ResponderProvided) => {
if (!result.destination) return; // invalid drop
const startIndex = result.source.index;
const endIndex = result.destination.index;
// Destructure because we know we always have 1 item.
const [removed] = items.splice(startIndex, 1);
items.splice(endIndex, 0, removed);
setItems(items);
console.log("start index is %o, end index is %o", startIndex, endIndex);
};
return (
<div>
<h1>Hello</h1>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="main">
{makeDroppableChildren(items)}
</Droppable>
</DragDropContext>
</div>
);
}
While it's still very ugly, I feel that overall this is a relatively good deal
when it comes to avoiding dealing with the HTML5 drag and drop API directly.
The abstraction is extremely leaky-to-nonexistent here, as you can see. You're
plugging a bunch of mandatory stuff into your HTML and while you can ignore
the detailed-contents of the stuff, you can't ignore the presence of the stuff
itself. Every innerRef
, *props
value here is the cruft from the mechanism
showing through. I must give this library its due, though: the error messages
from this library are extremely good, and the documentation is also decent.
Once you commit to introducing this piece of large bit of ugly boilerplate, you
do get a good amount for taking on this burden, so I think it's a good bet
overall.
I feel like there's probably a much more idiomatic way to do the currying approach above, but I'm not experienced enough with React yet to know what it is.