React flow - Customizing React Flow

From 탱이의 잡동사니
Jump to navigation Jump to search

Overview

Reactflow 정리 문서 원문은 이곳<ref>https://reactflow.dev/learn/customization/custom-nodes</ref>에서 확인할 수 있다.

Custom Nodes

A powerful feature of React Flow is the ability to add custom nodes. Within your custom nodes you can render everything you want. You can define multiple source and target handles and render form inputs or charts for example. In this section we will implement a node with an input field that updates some text in another part of the application.

Implementing the Custom Node

A custom node is a React component that is wrapped to provide basic functionality like selecting or dragging. From the wrapper component we are passing props like the position or the data besides other props. Let's start to implement the TextUpdaterNode. We are using the Handle component to be able to connect our custom node with other nodes and add an input field to the node:

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

const handleStype = {left: 10 };

function TextUpdaterNode({data}) {
  const onChange = useCallback((evt) => {
    console.log(evt.target.value);
  }, []);

  return(
  <>
    <Handle type="target" position={Position.Top} />
    <div>
      <label htmlFor="text">Text:</label>
      <input id="text" name="text" onChange={onChange} className="nodrag" />
    </div>
    <Handle type="source" position={Position.Botton} id="a" />
    <Handle
      type="source"
      position={Position.Botton}
      id="b"
      stype={handleStyle}
    </Handle>
  </>
  );
}

As you see we've added the class name "nodrag" to the input. This prevents dragging within the input field and lets us select text for example.

Adding the Node Type

You can add a new node type to React Flow by adding to the nodeTypes props. It's important that the nodeTypes are memoized or defined outside of the component. Otherwise React creates a new object on every render which leads to performance issues and bugs.

const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);

return <ReactFlow nodeTypes={nodeTypes} />;

After defining your new node type, you can use it by using the type node option:

const nodes = [
  {
    id: 'node-1',
    type: 'textUpdater',
    position: {x: 0, y: 0},
    data: {value: 123},
  },
];

After putting all together and adding some basic styles we get a custom node that prints text to the console.

Using Multiple Handles

As you can see we added two source handles to the node so that it has two outputs. If you want to connect other nodes with these specific handles, the node id is not enough but you also need to pass the specific handle id. In this case one handle has the id "a" and other one "b". Handle specific edges use the sourceHandle or targetHandle options that reference a handle within a node.

const initialEdges = [
  {id: 'edge-1', source: 'node-1', sourceHandle: 'a', target: 'node-2' },
  {id: 'edge-2', source: 'node-1', sourceHandle: 'b', target: 'node-3' },
];

In this case the source node is node-1 for both handles but the handle ids are different. One comes from handle id "a" and the other one from "b". Both edges also have different target nodes:

const initialNodes = [
  {
    id: "node-1",
    type: "textUpdater",
    position: { x: 0, y: 0 },
    data: { value: 123 },
  },
  {
    id: "node-2",
    type: "output",
    targetPosition: "top",
    position: { x: 0, y: 200 },
    data: { label: "node 2" },
  },
  {
    id: "node-3",
    type: "output",
    targetPosition: "top",
    position: { x: 200, y: 200 },
    data: { label: "node 3" },
  },
];

Note that if you are programmatically changing the position or number of handles in your custom node, you will need to use the useUpdateNodeInternals hook to properly notify ReactFlow of changes. From here you should be abel to build your custom nodes. In most cases we recommend to use custom nodes only. The built-in ones are just basic examples.

Custom edges

Like custom nodes, parts of a custom edge in React Flow are just React component: that means you can render anything you want along an edge!

A basic custom edge

An edge isn't much use to us if it doesn't render a path between two connected nodes. These paths are always SVG-based and are typically rendered using the <BaseEdge /> component. To calculate the actual SVG path to render, React Flow comes with some handy utility functions:

  • getBezierPath
  • getSimpleBezierPath
  • getSmoothStepPath
  • getStraightPath

To kick start out custom edge, we'll just render a straight path between the source and target.

import {BaseEdge, getStraightPath} from 'reactflow';

export default function CustomEdge({id, sourceX, sourceY, targetX, targetY}) {
  const [edgePath] = getStraightPath({
    sourceX,
    sourceY,
    targetX,
    targetY,
  });

  return(
    <>
      <BaseEdge id={id} path={edgePath} />
    </>
  );
}

This gives us a straight edge that behaves the same as the default "straight" edge type. To use it, we also need to update the edgeTypes prop on the <ReactFlow /> component.

It's important to define the edgeTypes object outside of the component or to use React's useMemo hook to prevent unnecessary re-renders. React Flow will show a warning in the console if you forget to do this.

import ReactFlow from 'reactflow'
import CustomEdge from './CustomEdge'

const edgeTypes = {
  'custom-edge': CustomEdge
}

export function Flow() {
  return <ReactFlow edgeTypes={edgeTypes}... />
}

After defining the edgeTypes objects, we can use our new custom edge by setting the type field of an edge to "custom-edge".