β€’

tech

Frontend, Drag & Drop, AI and XState

Do you know how to implement Drag & Drop on the web? Well, ask AI. This is how I implemented Drag & Drop starting from AI, all the way to XState, state machines, and actors.


Sandro Maglione

Sandro Maglione

Software

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:

Start from AI when something is unknown.
Start from AI when something is unknown.

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.

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 state
  • Dragging: parent state
    • OutsideZone: dragging outside drop zone
    • InsideZone: dragging inside drop zone
    • Released: back to Idle

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 πŸ§™

And the learning material is growing day by day:

Effect: the unreadable library that captured my heart, by Matt Pocock

Once again: Effect: Beginners Complete Getting Started πŸ‘ˆ

See you next πŸ‘‹

Start here.

Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights

Not convinced? Well, let me tell you more about it