reactjsreact-flow

Custom nodes in react-flow; saving additional data to a node after it has been created


This is my first introduction to react-flow. I am looking to create a custom node where after creation, the user can enter information in the node and save/display it. From the react-flow documentation on custom nodes, they have a similar example where they created a TextUpdaterNode that console.logs the user input.

Instead of logging it it via console, I am looking for a way to save the information to the node itself and display it on the node. For example, if a user were to enter "24, male" into the input and hit the "enter" key, I want the node to be updated with that information.

What are the ways I can go about doing this?


Solution

  • What you're trying to do needs a little more than that:

    You can see alive example here: https://codesandbox.io/s/dank-waterfall-8jfcf4?file=/src/App.js

    Basically, you need:

     const onAdd = useCallback(() => {
       const newNode = {
         id: getNodeId(),
         data: { label: `${state.name} (${state.age})` },
         position: {
           x: 0,
           y: 0 + (nodes.length + 1) * 20
         }
       };
       setNodes((nds) => nds.concat(newNode));
     }, [nodes, setNodes, state.name, state.age]);
    
      const onEdit = () => {
        setNodes((nds) =>
          nds.map((node) => {
            if (node.id === editState.id) {
              node.data = {
                ...node.data,
                label: `${node.id} - ${editState.name} (${editState.age})`
              };
            }
    
            return node;
          })
        );
      };
    

    The whole code looks like:

    import React, { useState, useCallback } from "react";
    import ReactFlow, {
      ReactFlowProvider,
      useNodesState,
      useEdgesState
    } from "react-flow-renderer";
    
    import "./styles.css";
    
    const getNodeId = () => `randomnode_${+new Date()}`;
    
    const initialNodes = [
      { id: "1", data: { label: "Node 1" }, position: { x: 100, y: 100 } },
      { id: "2", data: { label: "Node 2" }, position: { x: 100, y: 200 } }
    ];
    
    const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];
    
    const FlowExample = () => {
      const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
      const [edges] = useEdgesState(initialEdges);
      const [state, setState] = useState({ name: "", age: "" });
    
      const onAdd = useCallback(() => {
        const newNode = {
          id: getNodeId(),
          data: { label: `${state.name} (${state.age})` },
          position: {
            x: 0,
            y: 0 + (nodes.length + 1) * 20
          }
        };
        setNodes((nds) => nds.concat(newNode));
      }, [nodes, setNodes, state.name, state.age]);
    
      return (
        <div>
          Name:{" "}
          <input
            type="text"
            onChange={(e) => {
              setState((prev) => ({ ...prev, name: e.target.value }));
            }}
          />
          Age:{" "}
          <input
            type="text"
            onChange={(e) => {
              setState((prev) => ({ ...prev, age: e.target.value }));
            }}
          />
          <button onClick={onAdd}>add node</button>
          <div style={{ width: "500px", height: "500px" }}>
            <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
          </div>
        </div>
      );
    };
    
    export default () => (
      <ReactFlowProvider>
        <FlowExample />
      </ReactFlowProvider>
    );
    

    Also, with edit:

    import React, { useState, useCallback } from "react";
    import ReactFlow, {
      ReactFlowProvider,
      useNodesState,
      useEdgesState
    } from "react-flow-renderer";
    
    import "./styles.css";
    
    const getNodeId = () => `${String(+new Date()).slice(6)}`;
    
    const initialNodes = [
      { id: "1", data: { label: "Node 1" }, position: { x: 100, y: 100 } },
      { id: "2", data: { label: "Node 2" }, position: { x: 100, y: 200 } }
    ];
    
    const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];
    
    const FlowExample = () => {
      const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
      const [edges] = useEdgesState(initialEdges);
      const [state, setState] = useState({ name: "", age: "" });
      const [editState, setEditState] = useState({ id: "", name: "", age: "" });
    
      const onEdit = () => {
        setNodes((nds) =>
          nds.map((node) => {
            if (node.id === editState.id) {
              node.data = {
                ...node.data,
                label: `${node.id} - ${editState.name} (${editState.age})`
              };
            }
    
            return node;
          })
        );
      };
    
      const onAdd = () => {
        const id = getNodeId();
        const newNode = {
          id,
          data: { label: `${id} - ${state.name} (${state.age})` },
          position: {
            x: 0,
            y: 0 + (nodes.length + 1) * 20
          }
        };
        setNodes((nds) => nds.concat(newNode));
      };
    
      return (
        <div>
          Name:{" "}
          <input
            type="text"
            onChange={(e) => {
              setState((prev) => ({ ...prev, name: e.target.value }));
            }}
          />
          Age:{" "}
          <input
            type="text"
            onChange={(e) => {
              setState((prev) => ({ ...prev, age: e.target.value }));
            }}
          />
          <button onClick={onAdd}>add node</button>
          <br />
          Id:{" "}
          <input
            type="text"
            onChange={(e) => {
              setEditState((prev) => ({ ...prev, id: e.target.value }));
            }}
          />
          Name:{" "}
          <input
            type="text"
            onChange={(e) => {
              setEditState((prev) => ({ ...prev, name: e.target.value }));
            }}
          />
          Age:{" "}
          <input
            type="text"
            onChange={(e) => {
              setEditState((prev) => ({ ...prev, age: e.target.value }));
            }}
          />
          <button onClick={onEdit}>Edit node</button>
          <div style={{ width: "500px", height: "500px" }}>
            <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} />
          </div>
        </div>
      );
    };
    
    export default () => (
      <ReactFlowProvider>
        <FlowExample />
      </ReactFlowProvider>
    );
    
    

    A more helpful example from documentation would be:

    But you have to remove all the extra information (Also, you can use it to go deeper!)