HypergoatTheGr8
HypergoatTheGr8

Reputation: 11

React flow switching handle types not working

I am trying to make a button on a react flow node that switches the type of the handles (target or source) and it does not work. In the devtools i can see that the handles ares switching types, but when I am connecting nodes together the handles behave like the type has not been changed.

I have tried toggling handle types, using different configurations for logic and how I toggle the types, and updating the parent state. Nothing has worked to correctly change the type of the handle. I expected for something to make it so that the type of the right and left handles switches from target to source or source to target, which it did, but it does not behave like that in the canvas.

here is the code for the node with the switch handles button:

import { memo } from 'react';
import { Handle, Position } from 'reactflow';

interface AgentNodeData {
  type: string;
  name: string;
  systemMessage?: string;
  leftHandleType: 'source' | 'target';
  rightHandleType: 'source' | 'target';
  onChange: (id: string, field: string, value: string) => void;
}

interface AgentNodeProps {
  data: AgentNodeData;
  id: string;
}

const getArrowIcon = (handleType: 'source' | 'target', position: Position) => {
  if (position === Position.Left) {
    return handleType === 'source' ? 'fa-arrow-circle-right' : 'fa-arrow-circle-left';
  } else if (position === Position.Right) {
    return handleType === 'source' ? 'fa-arrow-circle-left' : 'fa-arrow-circle-right';
  }
  return 'fa-arrow-circle-right'; // Default case
};

const AgentNode = memo(({ data, id }: AgentNodeProps) => {
  const handleSwitchClick = () => {
    const newLeftHandleType = data.leftHandleType === 'source' ? 'target' : 'source';
    const newRightHandleType = data.rightHandleType === 'source' ? 'target' : 'source';
  
    console.log('Switching handles:', {
      id,
      newLeftHandleType,
      newRightHandleType,
    });
  
    data.onChange(id, 'leftHandleType', newLeftHandleType);
    data.onChange(id, 'rightHandleType', newRightHandleType);
  };

  return (
    <div className="px-4 py-2 shadow-md rounded-lg bg-card border border-border relative">
      <div className="flex items-center gap-2">
        <div className="text-lg font-bold text-foreground flex items-center gap-2">
          {data.type}
          <button
            className="hover:scale-110 transition-transform duration-300"
            onClick={handleSwitchClick}
            aria-label="Switch arrow directions"
          >
            <i className="fas fa-arrow-right-arrow-left"></i>
          </button>
        </div>
      </div>
      <div className="flex flex-col gap-2 mt-2">
        <input
          type="text"
          placeholder="Name"
          defaultValue={data.name}
          onChange={(e) => data.onChange(id, 'name', e.target.value)}
          className="text-sm text-muted-foreground bg-transparent border-none focus:outline-none w-full"
        />
        <input
          type="text"
          placeholder="System Message"
          defaultValue={data.systemMessage}
          onChange={(e) => data.onChange(id, 'systemMessage', e.target.value)}
          className="text-sm text-muted-foreground bg-transparent border-none focus:outline-none w-full"
        />
      </div>

        <Handle
          type={data.leftHandleType}
          position={Position.Left}
          className="!bg-transparent !border-none !w-auto !h-auto"
          key={`left-${data.leftHandleType}`} // Force re-render when handle type changes
        >
          <i
            className={`fas ${getArrowIcon(data.leftHandleType, Position.Left)} text-primary`}
            style={{ fontSize: '1.2rem', pointerEvents: 'none' }}
          />
        </Handle>
        
        <Handle
          type={data.rightHandleType}
          position={Position.Right}
          className="!bg-transparent !border-none !w-auto !h-auto"
          key={`right-${data.rightHandleType}`} // Force re-render when handle type changes
        >
          <i
            className={`fas ${getArrowIcon(data.rightHandleType, Position.Right)} text-primary`}
            style={{ fontSize: '1.2rem', pointerEvents: 'none' }}
          />
        </Handle>
    </div>
  );
});

AgentNode.displayName = 'AgentNode';

export default AgentNode;

Upvotes: 1

Views: 124

Answers (1)

HypergoatTheGr8
HypergoatTheGr8

Reputation: 11

I decided to switch the position of the handles instead of the type, and that has worked well. '''

import { memo } from 'react'; import { Handle, Position, useUpdateNodeInternals } from 'reactflow';

interface AgentNodeData {   type: string;   name: string;   systemMessage?: string;   handlePosition: 'left-right' | 'right-left'; onChange: (id: string, field: string, value: string) => void; }

interface AgentNodeProps {   data: AgentNodeData;   id: string; }

const getArrowIcon = (handleType: 'source' | 'target', position: Position) => {   if (position === Position.Left) {
    return handleType === 'source' ? 'fa-arrow-circle-left' : 'fa-arrow-circle-right';   } else if (position === Position.Right) {
    return handleType === 'source' ? 'fa-arrow-circle-right' : 'fa-arrow-circle-left';   }   return 'fa-arrow-circle-right'; // Default case };

const AgentNode = memo(({ data, id }: AgentNodeProps) => {   const updateNodeInternals = useUpdateNodeInternals();

  const handleSwitchClick = () => {
    // Toggle the handle positions
    const newHandlePosition = data.handlePosition === 'left-right' ? 'right-left' : 'left-right';
    data.onChange(id, 'handlePosition', newHandlePosition);

    // Update node internals to reflect the new handle positions
    updateNodeInternals(id);   };

  return (
    <div className="px-4 py-2 shadow-md rounded-lg bg-card border border-border relative">
      <div className="flex items-center gap-2">
        <div className="text-lg font-bold text-foreground flex items-center gap-2">
          {data.type}
          {/* Arrow-right-arrow-left button */}
          <button
            className="hover:scale-110 transition-transform duration-300"
            onClick={handleSwitchClick}
            aria-label="Switch handle positions"
          >
            <i className="fas fa-arrow-right-arrow-left"></i>
          </button>
        </div>
      </div>
      <div className="flex flex-col gap-2 mt-2">
        <input
          type="text"
          placeholder="Name"
          defaultValue={data.name}
          onChange={(e) => data.onChange(id, 'name', e.target.value)}
          className="text-sm text-muted-foreground bg-transparent border-none focus:outline-none w-full"
        />
        <input
          type="text"
          placeholder="System Message"
          defaultValue={data.systemMessage}
          onChange={(e) => data.onChange(id, 'systemMessage', e.target.value)}
          className="text-sm text-muted-foreground bg-transparent border-none focus:outline-none w-full"
        />
      </div>

      {data.handlePosition === 'left-right' ? (
        <>
          <Handle
            type="target"
            position={Position.Left}
            className="!bg-transparent !border-none !w-auto !h-auto"
            key={`left-${data.handlePosition}`} // Force re-render when handle position changes
          >
            <i
              className={`fas ${getArrowIcon('target', Position.Left)} text-primary`}
              style={{ fontSize: '1.2rem', pointerEvents: 'none' }}
            />
          </Handle>
          <Handle
            type="source"
            position={Position.Right}
            className="!bg-transparent !border-none !w-auto !h-auto"
            key={`right-${data.handlePosition}`} // Force re-render when handle position changes
          >
            <i
              className={`fas ${getArrowIcon('source', Position.Right)} text-primary`}
              style={{ fontSize: '1.2rem', pointerEvents: 'none' }}
            />
          </Handle>
        </>
      ) : (
        <>
          <Handle
            type="source"
            position={Position.Left}
            className="!bg-transparent !border-none !w-auto !h-auto"
            key={`left-${data.handlePosition}`} // Force re-render when handle position changes
          >
            <i
              className={`fas ${getArrowIcon('source', Position.Left)} text-primary`}
              style={{ fontSize: '1.2rem', pointerEvents: 'none' }}
            />
          </Handle>
          <Handle
            type="target"
            position={Position.Right}
            className="!bg-transparent !border-none !w-auto !h-auto"
            key={`right-${data.handlePosition}`} // Force re-render when handle position changes
          >
            <i
              className={`fas ${getArrowIcon('target', Position.Right)} text-primary`}
              style={{ fontSize: '1.2rem', pointerEvents: 'none' }}
            />
          </Handle>
        </>
      )}
    </div>   ); });

AgentNode.displayName = 'AgentNode';

export default AgentNode;

'''

Upvotes: 0

Related Questions