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

React类组件:修复“无法在已卸载组件更新状态”错误

解决React组件卸载后执行状态更新的错误

错误原因

当用户选择“Join a Game”并点击Submit后,组件将didGetUserName设为true,触发<Redirect>跳转,此时CreateNewGame组件会被卸载。但之前在componentDidMount中注册的socket.on("addNewRoom")事件监听并未被移除,当后端返回addNewRoom事件时,回调函数里的this.setState会尝试在已卸载的组件上更新状态,从而引发错误。

解决方案

需要做两个关键处理:

  • 在组件卸载时移除socket事件监听,避免无效回调执行
  • 添加组件挂载状态标志,确保只有组件处于挂载状态时才执行状态更新

修改后的代码

import React from "react";
import { Redirect } from "react-router-dom";
import uuid from "uuid/v4";
import { ColorContext } from "../context/colorcontext";
const socket = require("../connection/socket").socket;

/**
 * Onboard is where we create the game room.
 */

class CreateNewGame extends React.Component {
  state = {
    didGetUserName: false,
    inputText: "",
    gameId: "",
  };

  constructor(props) {
    super(props);
    this.textArea = React.createRef();
    this.textArea1 = React.createRef();
    // 添加标志位跟踪组件是否挂载
    this._isMounted = false;
    // 保存socket回调的引用,方便后续移除监听
    this.handleAddNewRoom = this.handleAddNewRoom.bind(this);
  }

  handleAddNewRoom = (theroomId) => {
    console.log(theroomId.rm);
    socket.emit("createNewGame", theroomId.rm + "1");
    // 仅当组件挂载时才更新状态
    if (this._isMounted) {
      this.setState({
        gameId: theroomId.rm + "1",
      });
    }
  };

  sendJoinTournament = () => {
    const url = document.getElementById("urlBox").value;
    const roomId = url.slice(27, 63);

    socket.emit("checkRoomSize", roomId);

    if (this._isMounted) {
      this.setState({
        gameId: roomId,
      });
    }
  };

  componentDidMount() {
    this._isMounted = true;
    // 使用保存的回调引用注册事件
    socket.on("addNewRoom", this.handleAddNewRoom);
  }

  componentWillUnmount() {
    this._isMounted = false;
    // 移除socket事件监听
    socket.off("addNewRoom", this.handleAddNewRoom);
  }

  sendCreateNewGame = () => {
    const newGameRoomId = uuid();

    if (this._isMounted) {
      this.setState({
        gameId: newGameRoomId,
      });
    }

    socket.emit("createNewGame", newGameRoomId);
  };

  typingUserName = () => {
    const typedText = this.textArea.current.value;

    if (this._isMounted) {
      this.setState({
        inputText: typedText,
      });
    }
  };

  render() {
    return (
      <React.Fragment>
        {this.state.didGetUserName ? (
          <Redirect to={"/game/" + this.state.gameId}>
            <button
              className="btn btn-success"
              style={{
                marginLeft: String(window.innerWidth / 2 - 60) + "px",
                width: "120px",
              }}
            >
              Start Game
            </button>
          </Redirect>
        ) : (
          <div>
            <h1
              style={{
                textAlign: "center",
                marginTop: String(window.innerHeight / 3) + "px",
              }}
            >
              Your Username:
            </h1>
            <input
              style={{
                marginLeft: String(window.innerWidth / 2 - 120) + "px",
                width: "240px",
                marginTop: "62px",
              }}
              ref={this.textArea}
              onInput={this.typingUserName}
            ></input>
            <br></br>
            <input
              style={{
                marginLeft: String(window.innerWidth / 2 - 120) + "px",
                width: "10px",
                marginTop: "31px",
              }}
              type="radio"
              id="myRadio"
              name="createRadio"
              value="HTML"
              ref={this.radiobutton1}
            ></input>
              <label htmlFor="html">Create New Game</label>
            <br></br>
            <input
              style={{
                marginLeft: String(window.innerWidth / 2 - 120) + "px",
                width: "10px",
                marginTop: "31px",
              }}
              type="radio"
              id="myRadio1"
              name="joinRadio"
              value="HTML"
              ref={this.radiobutton2}
            ></input>
              <label htmlFor="html">Join a Game</label>
            <input
              style={{
                marginLeft: String(window.innerWidth / 2 - 120) + "px",
                width: "240px",
                marginTop: "62px",
              }}
              placeholder="paste link"
              id="urlBox"
              ref={this.textArea1}
            ></input>
            <button
              className="btn btn-primary"
              style={{
                marginLeft: String(window.innerWidth / 2 - 60) + "px",
                width: "120px",
                marginTop: "62px",
              }}
              disabled={!(this.state.inputText.length > 0)}
              onClick={() => {
                if (document.getElementById("myRadio").checked) {
                  this.props.didRedirect();
                  this.props.setUserName(this.state.inputText);
                  if (this._isMounted) {
                    this.setState({
                      didGetUserName: true,
                    });
                  }
                  this.sendCreateNewGame();
                }

                if (document.getElementById("myRadio1").checked) {
                  this.sendJoinTournament();
                  if (this._isMounted) {
                    this.setState({
                      didGetUserName: true,
                    });
                  }
                }
              }}
            >
              Submit
            </button>
          </div>
        )}
      </React.Fragment>
    );
  }
}

const Onboard = (props) => {
  const color = React.useContext(ColorContext);

  return (
    <CreateNewGame
      didRedirect={color.playerDidRedirect}
      setUserName={props.setUserName}
    />
  );
};

export default Onboard;

关键修改点说明

  • 添加_isMounted标志位,在componentDidMount中设为true,componentWillUnmount中设为false,所有setState调用前都检查该标志位
  • 将socket.on("addNewRoom")的回调函数提取为类方法handleAddNewRoom,并保存引用,在componentWillUnmount中用socket.off移除该监听,避免组件卸载后回调仍被触发
  • 修正了代码中的小问题:将Ref改为ref,label的for改为htmlFor(React中JSX的属性规范)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 04:50:26