You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Flow自定义节点保存额外数据时遇只读属性错误

React-Flow自定义节点保存额外数据时"attribute is read-only"错误解决

错误原因

React Flow传递给自定义节点的data对象是受控状态的一部分,内部被设置为不可直接修改(可能被冻结),直接赋值data.attribute = formData会触发只读错误,同时这种修改也不会触发组件重新渲染。

解决方案

通过更新整个elements数组来修改节点数据,遵循React状态更新的受控原则:

1. 修改自定义节点组件

添加id和onUpdateNode属性,将数据更新逻辑委托给父组件:

import { useState } from 'react';
import { Handle, Position } from 'react-flow-renderer';
import { Modal } from "react-bootstrap";
import { Form } from 'react-bootstrap';
import { triggerLimitationType } from "../../../enums/triggerLimitationType";
import _ from 'lodash';
import i18next from "../../../i18n";

function CustomNode({ data, id, onUpdateNode }) {
  const [show, setShow] = useState(false);
  const handleClose = () => setShow(false);
  const handleShow = () => setShow(true);
  // 从节点现有data中初始化表单数据,保证数据同步
  const [formData, setFormData] = useState(data.attribute || { type: 0 });

  const handleChange = event => {
    const { name, value } = event.target;
    const newFormData = { ...formData, [name]: value };
    setFormData(newFormData);
    // 通知父组件更新节点数据
    onUpdateNode(id, { attribute: newFormData });
  }

  return (
    <div>
      <div onDoubleClick={handleShow}>
        <Handle type="target" position={Position.Top} />
        <div className="node-icon" >
          <i className="feather icon-skip-back"></i>
        </div>
        <Handle type="source" position={Position.Bottom} id="b" />
      </div>

      {show && (
        <Modal show={show} onHide={handleClose} className="right fade">
          <Modal.Header closeButton>
            <Modal.Title>{i18next.t("Trigger")} : {i18next.t("VisitorReturn")}</Modal.Title>
          </Modal.Header>

          <Modal.Body>
            <Form.Group>
              <Form.Label>Type</Form.Label>
              <Form.Control required as="select" value={formData.type} name="type" onChange={handleChange}>
                {_.map(triggerLimitationType, (value, key) => (
                  <option value={value} key={value}>{key}</option>
                ))}
              </Form.Control>
              <Form.Control.Feedback type="invalid">Please choose a type.</Form.Control.Feedback>
            </Form.Group>
          </Modal.Body>
        </Modal>
      )}
    </div>
  );
}

export default CustomNode;

2. 父组件实现节点更新逻辑

定义onUpdateNode函数,通过更新elements数组修改对应节点的data:

// 父组件中定义节点更新方法
const onUpdateNode = (nodeId, updates) => {
  // 函数式更新确保拿到最新的elements状态
  setElements(prevElements => 
    prevElements.map(node => {
      if (node.id === nodeId) {
        // 合并原有数据与更新内容
        return { ...node, data: { ...node.data, ...updates } };
      }
      return node;
    })
  );
  // 同步redux状态(如果使用redux)
  dispatch(updateNodeData(nodeId, updates));
};

// ReactFlow组件中配置自定义节点类型,传递onUpdateNode
<ReactFlow
  elements={elements}
  nodeTypes={{
    // 替换成你的自定义节点type
    VisitorReturn: props => <CustomNode {...props} onUpdateNode={onUpdateNode} />
  }}
  // 其他属性...
/>

3. 修正节点创建逻辑

使用函数式更新获取最新的elements状态,避免旧值问题:

const onDrop = (event) => {
  event.preventDefault();

  const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
  const type = event.dataTransfer.getData("type");

  const position = reactFlowInstance.project({
    x: event.clientX - reactFlowBounds.left,
    y: event.clientY - reactFlowBounds.top
  });

  const newNode = {
    id: getId(),
    position,
    type: type,
    data: {
      label: `${type} node`, 
      attribute: { type: 0 }
    }
  };

  // 函数式更新elements
  setElements(prevElements => [...prevElements, newNode]);
  dispatch(updateElements(newNode));
};

内容的提问来源于stack exchange,提问作者ar.gorgin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 06:45:30