What does it take to implement Drag & Drop on the web? π€
Not much once you have XState and AI on your side π€
This is how I went about implementing Drag & Drop from start to end π
Unknown? AI!
I haven't work on any drag & drop for a few years now. Which is a lot on the web π€―
Not a problem in this new age, AI solved it all!
No more "searching on the web", reading (outdated) articles, Reddit (or StackOverflow, remember?).
A single short query on Claude (for free) and done:
That's all that I need to jump into the implementation.
What about Cursor?
Legit question: why not just let Cursor implement it all inside your IDE? π€
Thing is, AI writing code works "decent" when the scope of the problem is limited (e.g. write a single function).
But in (most of) my cases, each new features has to integrate in a larger (large!) system π¬
I needed the drag & drop to be part of a nested actor system implemented with XState, which included many other features (e.g. multiple dialogs, nested actors, async requests).
I have many conventions and assumptions, the AI won't get it all π
That's why I just ask for the concepts first (no implementation).
AI code: the good parts
Claude gave my this:
import { useState } from 'react';
function DragDropExample() {
const [draggedData, setDraggedData] = useState(null);
const [dropResult, setDropResult] = useState('');
const handleDragStart = (e) => {
const data = 'Hello from drag source!';
e.dataTransfer.setData('text/plain', data);
e.dataTransfer.effectAllowed = 'move';
setDraggedData(data);
};
const handleDragOver = (e) => {
e.preventDefault(); // Required to allow drop
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e) => {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
setDropResult(`Dropped: ${data}`);
};
return (
<div>
<div
draggable
onDragStart={handleDragStart}
style={{
padding: '20px',
backgroundColor: 'lightblue',
cursor: 'grab',
margin: '10px'
}}
>
Drag me!
</div>
<div
onDragOver={handleDragOver}
onDrop={handleDrop}
style={{
padding: '40px',
border: '2px dashed #ccc',
minHeight: '100px',
margin: '10px'
}}
>
Drop zone: {dropResult || 'Drop here'}
</div>
</div>
);
}
I don't care about the whole code, I am looking for patterns and snippets:
- JSX structure (
draggable
element and "Drop zone") - React events (
onDragStart
,onDragOver
,onDrop
) - DataTransfer API (and how to use it)
- Styling
That's where the "engineering" part comes in. You gather the materials, and then engineer the system with them ποΈ
XState all together
The second "secret" ingredient is xstate
.
I am working on the most complex state management system I ever did It's a breeze with XState π Some features I am using π Dynamic (nested) actors π enqueueActions π Nested states π Guards π stopChild π after
Drag & Drop is all about states, a state machine is ideal.
Note: that's the same for most state management problems ππΌββοΈ
You may think you only have "Dragging" and "NoDragging". But no! To give you an idea:
Idle
: initial stateDragging
: parent stateOutsideZone
: dragging outside drop zoneInsideZone
: dragging inside drop zoneReleased
: back toIdle
Then you must also consider some sort of
Pending
state while sending an API request to confirm the action. What about optimistic state updates? Undo? Redo?Glad there is
xstate
π«‘
It all anchors on this single principle: no business logic inside components π
Which gets you to something like this (simplified):
export default function DragAndDrop({ dragAndDropActor, sectionId }: {
dragAndDropActor: ActorRefFrom<typeof machine>;
sectionId: string;
}) {
const { isInsideZone, isDragging, draggedSectionId, draggedItemSectionId } =
useSelector(dragAndDropActor, (snapshot) => ({
draggedSectionId: snapshot.context.sectionId,
draggedItemSectionId: snapshot.context.itemSectionId,
isInsideZone: snapshot.matches({ Dragging: "InsideZone" }),
isDragging: snapshot.matches("Dragging"),
}), /** TODO: Implement `compare` */);
return (
<div
className={cn(
isInsideZone &&
? "border-blue"
: isDragging
? "border-grey-lightest"
: "border-transparent"
)}
onDragEnter={() =>
dragAndDropActor.send({ type: "drag.enter", sectionId })
}
onDragLeave={() =>
dragAndDropActor.send({ type: "drag.leave" })
}
onDragOver={(event) =>
dragAndDropActor.send({ type: "drag.over", sectionId, event })
}
onDrop={(event) =>
dragAndDropActor.send({ type: "drop", event })
}
>
{/* ... */}
</div>
);
}
Basically a mapping of React event to XState event ππΌββοΈ
This is the workflow I use for most features: AI the concepts, engineer states (
xstate
), link component and actor(s) πͺ
Everyone is joining the effect
π§
The unreadable library that somehow captured my heart
And the learning material is growing day by day:
Once again: Effect: Beginners Complete Getting Started π
See you next π