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

React卸载组件后状态更新报错:isMounted检测无效求助

问题:React组件卸载后状态更新引发内存泄漏

我在React应用中用axios从API获取数据,需要在多个嵌套组件中展示。相关代码如下:

const Building = () => {
  const { bid } = useParams();
  const { userAccessToken } = useAuth();
  const [buildingData, setBuildingData] = useState([]);
  const bearerToken = `Bearer ${userAccessToken}`;
  React.useEffect(() => {
    let isMounted = true;
    const axiosConfig = {
      headers: { Authorization: bearerToken },
    };
    axios
      .get(
        "http://localhost:3001/building?requestedlid=2&requestedbid=" + bid,
        axiosConfig
      )
      .then(function (response) {
        if (isMounted) {
          setBuildingData(response.data[0]);
        }
      })
      .catch(function (error) {
        // handle error
        console.log(error);
      });
    return () => {
      isMounted = false;
    };
  }, [bearerToken, bid]);

  return (
    <React.Fragment>
      <Helmet title="Building Profile" />
      <Container fluid className="p-0">
        <Breadcrumb className="float-end mt-2">
          <Breadcrumb.Item href="/dashboard/default">Home</Breadcrumb.Item>
          <Breadcrumb.Item href="/buildings/portfolio">
            Portfolio
          </Breadcrumb.Item>
          <Breadcrumb.Item active>Building Profile</Breadcrumb.Item>
        </Breadcrumb>
        <h1 className="h3 mb-3">
          Building Profile
          <OffcanvasHelp
            id="buildingprofile"
            name="Building Profile"
            scroll
            backdrop
          />
        </h1>
        <div className="clearfix"></div>
        <Row>
          <Col xl="8">
            <BuildingProfile
              name={buildingData.building_name}
              status={buildingData.status}
              description={buildingData.description}
              keycontacts={buildingData.key_contacts}
              created={buildingData.stats.created_date}
              golive={buildingData.stats.golive_date}
            />
            <Rooms />
          </Col>
          <Col xl="4">
            <AccountManager />
            <Map location={buildingData.location} />
            <GetSupport type="commercial" />
          </Col>
        </Row>
      </Container>
    </React.Fragment>
  );
};

遇到的错误:

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

我尝试了StackOverflow提到的isMounted布尔值检测组件挂载状态,但问题没解决。奇怪的是,只有给BuildingProfile组件传递以下两个props时才触发错误:

created={buildingData.stats.created_date}
golive={buildingData.stats.golive_date}

不传递这两个props的话,其他功能都正常。肯定遗漏了某些细节,求解决思路或提示。


更新:附上BuildingProfile组件代码

const BuildingProfile = ({
  name,
  status,
  description,
  created,
  golive,
  keycontacts,
}) => {
  // Modal config for "Deactivate Building"
  const initOpenModals = () => {
    let modals = {};
    colors.forEach((color, index) => {
      modals = Object.assign({}, modals, { [index]: false });
    });
    console.log(modals);
    return modals;
  };

  const [openModals, setOpenModals] = useState(() => initOpenModals());

  const toggle = (index) => {
    // Toggle selected element
    setOpenModals((openModals) =>
      Object.assign({}, openModals, { [index]: !openModals[index] })
    );
  };

  const notyf = useContext(NotyfContext);
  const [type] = useState("success");
  const [duration] = useState("5000");
  const [ripple] = useState(true);
  const [dismissible] = useState(false);
  const [positionX] = useState("right");
  const [positionY] = useState("top");

  const navigate = useNavigate();

  return (
    <Card>
      <Card.Header className="mb-0 pb-0">
        <Card.Title className="mb-0">
          <IsAllowed to="edit:buildings">
            <div className="card-actions float-end">
              <Dropdown align="end">
                <Dropdown.Toggle as="a" bsPrefix="-">
                  <MoreHorizontal />
                </Dropdown.Toggle>
                <Dropdown.Menu>
                  <Dropdown.Item onClick={() => navigate("/buildings/edit")}>
                    Edit Building
                  </Dropdown.Item>
                  <React.Fragment key="deactivateBuilding">
                    <Dropdown.Item onClick={() => toggle("deactivateBuilding")}>
                      Deactivate Building
                    </Dropdown.Item>
                    <Modal
                      show={openModals["deactivateBuilding"]}
                      onHide={() => toggle("deactivateBuilding")}
                      centered
                    >
                      <Modal.Header closeButton>
                        <b>Admin Function:</b>&nbsp; Deactivate Building
                      </Modal.Header>
                      <Modal.Body className="m-3">
                        <p className="text-left mb-0">
                          Are you sure you want to deactivate the
                          <b>Bus Works</b> building? This will prevent the
                          building from showing up in the platform completely.
                        </p>
                      </Modal.Body>
                      <Modal.Footer>
                        <Button
                          variant="secondary"
                          onClick={() => toggle("deactivateBuilding")}
                        >
                          Close
                        </Button>{" "}
                        <Button
                          variant="danger"
                          onClick={() => {
                            toggle("deactivateBuilding");
                            notyf.open({
                              type,
                              message: "The building has been deactivated.",
                              duration,
                              ripple,
                              dismissible,
                              position: {
                                x: positionX,
                                y: positionY,
                              },
                            });
                          }}
                        >
                          Deactivate Building
                        </Button>
                      </Modal.Footer>
                    </Modal>
                  </React.Fragment>
                </Dropdown.Menu>
              </Dropdown>
            </div>
          </IsAllowed>
          <h1 className="mb-0 pb-0">{name}</h1>
          <Badge
            className={
              status === "Live Building"
                ? "my-2 btn-gradient inline"
                : "my-2 inline"
            }
            bg="success"
          >
            {status}
          </Badge>
        </Card.Title>
      </Card.Header>
      <Card.Body>
        <h5>Building Overview:</h5>
        <p className="mb-4">{description}</p>
        <div className="row">
          <div className="col-md-4">
            <div className="mb-4">
              <h5>Created Date</h5>
              <p>{created}</p>
            </div>
          </div>
          <div className="col-md-4">
            <div className="mb-4">
              <h5>Go-Live Date</h5>
              <p>{golive}</p>
            </div>
          </div>
          <div className="col-md-4">
            <div className="mb-4">
              <h5>Key Contacts</h5>
              <div>
                <span className="me-1">
                  <OverlayTrigger
                    placement="right"
                    overlay={
                      <Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
                    }
                  >
                    <img
                      src={avatar3}
                      width="28"
                      height="28"
                      className="rounded-circle me-2"
                      alt="Joe Bloggs"
                    />
                  </OverlayTrigger>
                </span>
                <span className="me-1">
                  <OverlayTrigger
                    placement="right"
                    overlay={
                      <Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
                    }
                  >
                    <img
                      src={avatar2}
                      width="28"
                      height="28"
                      className="rounded-circle me-2"
                      alt="Joe Bloggs"
                    />
                  </OverlayTrigger>
                </span>
                <span className="me-1">
                  <OverlayTrigger
                    placement="right"
                    overlay={
                      <Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
                    }
                  >
                    <img
                      src={avatar1}
                      width="28"
                      height="28"
                      className="rounded-circle me-2"
                      alt="Joe Bloggs"
                    />
                  </OverlayTrigger>
                </span>
                <Link to="#" className="d-inline-block text-muted fw-bold ms-2">
                  +2 more
                </Link>
              </div>
            </div>
          </div>
        </div>
      </Card.Body>
    </Card>
  );
};

解决思路

1. 修复初始状态与嵌套属性访问问题

你的buildingData初始值是数组[],但API返回的是对象response.data[0]。组件首次渲染时,buildingData.stats会直接报错(数组没有stats属性),这会导致组件渲染失败甚至提前卸载,而此时axios请求仍在进行,请求完成后执行setBuildingData就会触发"卸载组件更新状态"的错误。

两种修复方式:

  • 修改初始状态为对象:
    const [buildingData, setBuildingData] = useState({});
    
  • 传递props时用可选链处理嵌套属性:
    created={buildingData.stats?.created_date}
    golive={buildingData.stats?.golive_date}
    

2. 替换isMounted为axios请求取消机制

isMounted是React官方不推荐的反模式,正确做法是主动取消未完成的axios请求,推荐使用AbortController(CancelToken已被废弃):

React.useEffect(() => {
  const abortController = new AbortController();
  const axiosConfig = {
    headers: { Authorization: bearerToken },
    signal: abortController.signal // 绑定取消信号
  };
  axios
    .get(
      `http://localhost:3001/building?requestedlid=2&requestedbid=${bid}`,
      axiosConfig
    )
    .then(function (response) {
      setBuildingData(response.data[0]);
    })
    .catch(function (error) {
      if (!axios.isCancel(error)) { // 排除主动取消的错误
        console.log(error);
      }
    });
  return () => {
    abortController.abort(); // 组件卸载时取消请求
  };
}, [bearerToken, bid]);

3. 排查BuildingProfile组件的潜在问题

检查initOpenModals中用到的colors变量,如果它是外部可变变量,可能导致初始状态计算异常;另外确保toggle方法的函数式更新逻辑没有问题,不过这和当前错误关联度较低,可作为后续排查点。

总结

最可能的根源是初始状态不匹配导致的渲染错误,引发组件提前卸载,进而触发axios请求完成后的状态更新错误。先修复初始状态或嵌套属性访问,再用AbortController取消请求,即可解决问题。

内容的提问来源于stack exchange,提问作者Jonathan Nathanson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 17:45:40