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:
- Drag nodes from sidebar
- Drop on canvas
- Position accurately
- 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';
};
Then add to each node:
<div
draggable
onDragStart={(e) => onDragStart(e, node.type, node.variant, node.label)}
className="..."
>
{node.label}
</div>
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]
);
Step 3: Connect to React Flow
<ReactFlow
onDragOver={(e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}}
onDrop={onDrop}
onInit={setReactFlowInstance}
// ... other props
>
Key Challenges
1. Position Calculation
Problem: Screen coordinates ≠ Flow coordinates
Solution: Use screenToFlowPosition()
const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
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;
}
3. Node ID Generation
Problem: Duplicate IDs break React Flow
Solution: Incremental counter:
let id = 0;
const getId = () => `node_${id++}`;
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)
- Connection validation
- Node deletion
- Keyboard shortcuts
- Undo/redo
Code Repository
Follow along: GitHub - HarshAI
Live Demo
Try it: HarshAI Builder
Lessons Learned
- React Flow is powerful - But has a learning curve
- TypeScript helps - Catches data structure errors
- Test positioning - Screen vs flow coordinates trip you up
- 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)