Data Edge

An edge that displays one field from the source node’s data object.

UI Component: data-edge

index.tsx
import { useMemo } from "react";
import {
  BaseEdge,
  EdgeLabelRenderer,
  getBezierPath,
  getSmoothStepPath,
  getStraightPath,
  Position,
  useStore,
  type Edge,
  type EdgeProps,
  type Node,
} from "@xyflow/react";
 
export type DataEdge<T extends Node = Node> = Edge<{
  /**
   * The key to lookup in the source node's `data` object. For additional safety,
   * you can parameterize the `DataEdge` over the type of one of your nodes to
   * constrain the possible values of this key.
   *
   * If no key is provided this edge behaves identically to React Flow's default
   * edge component.
   */
  key?: keyof T["data"];
  /**
   * Which of React Flow's path algorithms to use. Each value corresponds to one
   * of React Flow's built-in edge types.
   *
   * If not provided, this defaults to `"bezier"`.
   */
  path?: "bezier" | "smoothstep" | "step" | "straight";
}>;
 
export function DataEdge({
  data = { path: "bezier" },
  id,
  markerEnd,
  source,
  sourcePosition,
  sourceX,
  sourceY,
  style,
  targetPosition,
  targetX,
  targetY,
}: EdgeProps<DataEdge>) {
  const nodeData = useStore((state) => state.nodeLookup.get(source)?.data);
  const [edgePath, labelX, labelY] = getPath({
    type: data.path ?? "bezier",
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
  });
 
  const label = useMemo(() => {
    if (data.key && nodeData) {
      const value = nodeData[data.key];
 
      switch (typeof value) {
        case "string":
        case "number":
          return value;
 
        case "object":
          return JSON.stringify(value);
 
        default:
          return "";
      }
    }
  }, [data, nodeData]);
 
  const transform = `translate(${labelX}px,${labelY}px) translate(-50%, -50%)`;
 
  return (
    <>
      <BaseEdge id={id} path={edgePath} markerEnd={markerEnd} style={style} />
      {data.key && (
        <EdgeLabelRenderer>
          <div
            className="bg-background text-foreground absolute rounded border px-1"
            style={{ transform }}
          >
            <pre className="text-xs">{label}</pre>
          </div>
        </EdgeLabelRenderer>
      )}
    </>
  );
}
 
/**
 * Chooses which of React Flow's edge path algorithms to use based on the provided
 * `type`.
 */
function getPath({
  type,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
}: {
  type: "bezier" | "smoothstep" | "step" | "straight";
  sourceX: number;
  sourceY: number;
  targetX: number;
  targetY: number;
  sourcePosition: Position;
  targetPosition: Position;
}) {
  switch (type) {
    case "bezier":
      return getBezierPath({
        sourceX,
        sourceY,
        targetX,
        targetY,
        sourcePosition,
        targetPosition,
      });
 
    case "smoothstep":
      return getSmoothStepPath({
        sourceX,
        sourceY,
        targetX,
        targetY,
        sourcePosition,
        targetPosition,
      });
 
    case "step":
      return getSmoothStepPath({
        sourceX,
        sourceY,
        targetX,
        targetY,
        sourcePosition,
        targetPosition,
        borderRadius: 0,
      });
 
    case "straight":
      return getStraightPath({
        sourceX,
        sourceY,
        targetX,
        targetY,
      });
  }
}
component-example.tsx
import { Handle, NodeProps, Position, useReactFlow, Node } from "@xyflow/react";
 
import { memo } from "react";
import { BaseNode, BaseNodeContent } from "@/registry/components/base-node";
import { Slider } from "@/components/ui/slider";
 
export type CounterNodeType = Node<{ value: number }>;
 
export const CounterNode = memo(({ id, data }: NodeProps<CounterNodeType>) => {
  const { updateNodeData } = useReactFlow();
 
  return (
    <BaseNode>
      <BaseNodeContent className="p-5">
        <Slider
          value={[data.value]}
          min={0}
          max={100}
          step={1}
          className="nopan nodrag w-24"
          onValueChange={([value]) => {
            updateNodeData(id, (node) => ({
              ...node.data,
              value,
            }));
          }}
        />
        <Handle type="source" position={Position.Bottom} />
      </BaseNodeContent>
    </BaseNode>
  );
});
 
CounterNode.displayName = "CounterNode";

Additional type safety

When creating new edges of this type, you can use TypeScript’s satisfies predicate along with the specific type of a node in your application to ensure the key property of the edge’s data is a valid key of the node’s data.

type CounterNode = Node<{ count: number }>;
 
const initialEdges = [
  {
    id: 'edge-1',
    source: 'node-1',
    target: 'node-2',
    type: 'dataEdge',
    data: {
      key: 'count',
    } satisfies DataEdge<CounterNode>,
  },
];

If you try to use a key that is not present in the node’s data, TypeScript will show an error message like:

ts: Type ‘“value”’ is not assignable to type ‘“count”‘.