SAUH
SAUH

Reputation: 11

Drag-drop custom nodes on react-flow

I am new to Reactflow and trying to make this basic drag-drop workflow builder to work but I m not able to drag-drop my nodes from left to right panel. page renders ok. I am able to drag the nodes but not drop

I do not get any error when I run this just that I am unable to drop the nodes

screenshot

Here is my code

// Frontend: React (ReactFlow-based Flow Builder)
import React, { useState, useCallback } from 'react';
import ReactFlow, { addEdge, Background, Controls, MiniMap, Handle } from 'react-flow-renderer';
import axios from 'axios';

const initialElements = [
  { id: '1', type: 'input', data: { label: 'Start' }, position: { x: 250, y: 5 } },
];

const nodeTypes = {
  textNode: ({ data }) => (
    <div style={{ padding: 10, border: '1px solid #777', borderRadius: 5 }}>
      <Handle type="target" position="top" />
      <div>{data.label}</div>
      <Handle type="source" position="bottom" />
    </div>
  ),
  conditionNode: ({ data }) => (
    <div style={{ padding: 10, border: '1px solid #f39c12', borderRadius: 5, backgroundColor: '#fdf3e7' }}>
      <Handle type="target" position="top" />
      <div>If: {data.ifCondition}</div>
      <div>Then: {data.thenAction}</div>
      <div>Else: {data.elseAction}</div>
      <Handle type="source" position="bottom" />
    </div>
  ),
  switchNode: ({ data }) => (
    <div style={{ padding: 10, border: '1px solid #3498db', borderRadius: 5, backgroundColor: '#eaf4fd' }}>
      <Handle type="target" position="top" />
      <div>Switch: {data.label}</div>
      <div>Cases:</div>
      {data.cases.map((caseItem, index) => (
        <div key={index}>{`Case ${index + 1}: ${caseItem}`}</div>
      ))}
      <Handle type="source" position="bottom" />
    </div>
  ),
  apiCallNode: ({ data }) => (
    <div style={{ padding: 10, border: '1px solid #27ae60', borderRadius: 5, backgroundColor: '#eafaf1' }}>
      <Handle type="target" position="top" />
      <div>API Call: {data.url}</div>
      <Handle type="source" position="bottom" />
    </div>
  ),
  setVariableNode: ({ data }) => (
    <div style={{ padding: 10, border: '1px solid #e74c3c', borderRadius: 5, backgroundColor: '#fdecea' }}>
      <Handle type="target" position="top" />
      <div>Set Variable: {data.variableName} = {data.value}</div>
      <Handle type="source" position="bottom" />
    </div>
  ),
};

const DraggableNode = ({ type, label }) => {
  const onDragStart = (event) => {
    event.dataTransfer.setData('application/reactflow', type);
    event.dataTransfer.effectAllowed = 'move';
  };

  return (
    <div
      draggable
      onDragStart={onDragStart}
      style={{ padding: '8px', marginBottom: '8px', backgroundColor: '#eee', cursor: 'grab' }}
    >
      {label}
    </div>
  );
};

const ChatBuilder = () => {
  const [elements, setElements] = useState(initialElements);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);

  const onConnect = (params) => setElements((els) => addEdge(params, els));

  const onDrop = useCallback(
    (event) => {
      event.preventDefault();
      if (!reactFlowInstance) {
        console.error('React Flow instance is not ready');
        return;
      }

      const type = event.dataTransfer.getData('application/reactflow');
      if (!type) {
        console.error('No node type found in drag data');
        return;
      }

      const reactFlowBounds = document.querySelector('.reactflow-wrapper').getBoundingClientRect();
      const position = reactFlowInstance.project({
        x: event.clientX - reactFlowBounds.left,
        y: event.clientY - reactFlowBounds.top,
      });

      console.log('Adding node at position:', position); // Debugging log

      const id = `node_${+new Date()}`;
      const newNode = {
        id,
        type,
        position,
        data: { label: `${type} Node` },
      };

      setElements((es) => es.concat(newNode));
    },
    [reactFlowInstance]
  );

  const onDragOver = useCallback((event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  const onLoad = useCallback((rfi) => {
    setReactFlowInstance(rfi);
    rfi.fitView();
  }, []);


  const saveFlow = async () => {
    try {
      const response = await axios.post('http://localhost:5000/api/saveFlow', {
        name: 'My First Flow',
        flowJson: elements,
      });
      alert(`Flow saved with ID: ${response.data.id}`);
    } catch (error) {
      console.error('Error saving flow', error);
      alert('Failed to save flow');
    }
  };  

  return (
    <div style={{ display: 'flex', height: '90vh' }}>
      <div style={{ width: '20%', padding: '10px', borderRight: '1px solid #ccc' }}>
        <h3>Node Types</h3>
        <DraggableNode type="textNode" label="Text Node" />
        <DraggableNode type="conditionNode" label="Condition Node" />
        <DraggableNode type="switchNode" label="Switch Node" />
        <DraggableNode type="apiCallNode" label="API Call Node" />
        <DraggableNode type="setVariableNode" label="Set Variable Node" />
        <button onClick={saveFlow}>Save Flow</button>
      </div>
      <div className="reactflow-wrapper" style={{ width: '80%', height: '100%' }}>
        <ReactFlow
          elements={elements}
          onConnect={onConnect}
          onElementsRemove={(elementsToRemove) =>
            setElements((els) => els.filter((e) => !elementsToRemove.includes(e)))
          }
          onLoad={onLoad}
          onNodeDragStop={(event, node) =>
            setElements((els) =>
              els.map((el) => (el.id === node.id ? { ...el, position: node.position } : el))
            )
          }
          onDrop={onDrop}
          onDragOver={onDragOver}
          nodeTypes={nodeTypes}
          snapToGrid={true}
        >
          <Background color="#aaa" gap={16} />
          <Controls />
          <MiniMap />
        </ReactFlow>
      </div>
    </div>
  );
};

export default ChatBuilder;

I should be able to drag and drop the nodes and build my workflow

Upvotes: 0

Views: 77

Answers (0)

Related Questions