DEV Community

Harshallahare
Harshallahare

Posted on

Building HarshAI Day 7: Drag & Drop Workflow Builder

Building HarshAI Day 7: Drag & Drop Workflow Builder (React Flow Tutorial)

Introduction

Welcome to Day 7 of building HarshAI, my AI automation platform. Today: implementing drag & drop for the workflow builder.

Previous: Day 6 - Custom Nodes

What We're Building

A drag & drop interface where users can:

  1. Drag nodes from sidebar
  2. Drop on canvas
  3. Position accurately
  4. Auto-save

Tech Stack

  • React Flow: Canvas library
  • TypeScript: Type safety
  • Next.js 14: Framework
  • Tailwind: Styling

Step 1: Update NodePanel

First, make nodes draggable:

const onDragStart = (event: React.DragEvent, nodeType: string, variant: string, label: string) => {
  event.dataTransfer.setData('application/reactflow', nodeType);
  event.dataTransfer.setData('variant', variant);
  event.dataTransfer.setData('label', label);
  event.dataTransfer.effectAllowed = 'move';
};
Enter fullscreen mode Exit fullscreen mode

Then add to each node:

<div
  draggable
  onDragStart={(e) => onDragStart(e, node.type, node.variant, node.label)}
  className="..."
>
  {node.label}
</div>
Enter fullscreen mode Exit fullscreen mode

Step 2: Handle Drop on Canvas

In the builder page:

const onDrop = useCallback(
  (event: React.DragEvent) => {
    event.preventDefault();

    const type = event.dataTransfer.getData('application/reactflow');
    const variant = event.dataTransfer.getData('variant');
    const label = event.dataTransfer.getData('label');

    const position = reactFlowInstance.screenToFlowPosition({
      x: event.clientX,
      y: event.clientY,
    });

    const newNode: Node = {
      id: getId(),
      type,
      position,
      data: {
        label,
        [type === 'trigger' ? 'triggerType' : 'actionType']: variant,
      },
    };

    setNodes((nds) => nds.concat(newNode));
  },
  [reactFlowInstance, setNodes]
);
Enter fullscreen mode Exit fullscreen mode

Step 3: Connect to React Flow

<ReactFlow
  onDragOver={(e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
  }}
  onDrop={onDrop}
  onInit={setReactFlowInstance}
  // ... other props
>
Enter fullscreen mode Exit fullscreen mode

Key Challenges

1. Position Calculation

Problem: Screen coordinates ≠ Flow coordinates

Solution: Use screenToFlowPosition()

const position = reactFlowInstance.screenToFlowPosition({
  x: event.clientX,
  y: event.clientY,
});
Enter fullscreen mode Exit fullscreen mode

2. Type Safety

Problem: TypeScript doesn't know node data structure

Solution: Define interfaces:

interface TriggerData {
  label: string;
  triggerType: string;
}

interface ActionData {
  label: string;
  actionType: string;
}
Enter fullscreen mode Exit fullscreen mode

3. Node ID Generation

Problem: Duplicate IDs break React Flow

Solution: Incremental counter:

let id = 0;
const getId = () => `node_${id++}`;
Enter fullscreen mode Exit fullscreen mode

Results

Before: Static nodes only
After: Full drag & drop

User experience:

  • Intuitive (drag from sidebar)
  • Fast (instant drop)
  • Accurate (pixel-perfect positioning)

Next Steps (Day 8)

  1. Connection validation
  2. Node deletion
  3. Keyboard shortcuts
  4. Undo/redo

Code Repository

Follow along: GitHub - HarshAI

Live Demo

Try it: HarshAI Builder

Lessons Learned

  1. React Flow is powerful - But has a learning curve
  2. TypeScript helps - Catches data structure errors
  3. Test positioning - Screen vs flow coordinates trip you up
  4. Auto-save is essential - Users lose work otherwise

Affiliate Disclosure

This post contains affiliate links for tools I use:

Conclusion

Day 7 complete! Drag & drop is working. Next: connection validation.

Follow for Day 8!


Building in public since Day 1. Questions? Ask below!

Top comments (0)