【问题标题】:React Native - React state update on an unmounted componentReact Native - 对未安装的组件进行 React 状态更新
【发布时间】:2020-01-02 16:05:59
【问题描述】:

screen shot of warning

我是 React native 的新手,在浏览我的应用程序组件时,我随机在不同的屏幕上遇到了这个警告

ExceptionsManager.js:82 警告:无法执行React state update on an unmounted component。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要修复,请取消 componentWillUnmount 方法中的所有订阅和异步任务。 在 LoginScreen 中(由 n 创建) 我尝试使用在 stackoverflow 中找到的不同解决方案,但它会发生,并且在此警告对我的应用程序的任何额外操作之后,我在我的应用程序冻结上使用 setState。

我尝试: - 在 componentDidMount 中将 this._isMounted 设置为 true,在 componentWillMount 中设置为 false。 - 使用 fetch 更改调用我的请求并使用 axios。 - 将导航更改为推送或替换导航从一个屏幕到另一个屏幕。

对我没有任何帮助。

这是我的登录屏幕代码:

import React, { Component } from "react";
import {
  StyleSheet,
  View,
  Image,
  ImageBackground,
  TextInput,
  Text,
  Alert,
  TouchableOpacity
} from "react-native";
import IMAGELOGINBG from "../../Images/login_bg.png"; //'../../index.js';
import { Button } from "react-native-elements";
import { LinearGradient } from "expo-linear-gradient";
import USERICON from "../../Images/user-icon.png";
import PASSWORDICON from "../../Images/pass-icon.png";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
// import {login} from './service.js';
import { BackHandler } from "react-native";
import ProgressLoader from "rn-progress-loader";
import withUnmounted from "@ishawnwang/withunmounted";

import yelp from "../api/yelp.js";

export function updateState(msg) {
  if (isMounted == true) {
    this.props.navigation.setParams({
      notificationCount: global.NotificationCount
    });
    this.setState(
      {
        visible: false,
        errorMsg: msg
      },
      () => {
        this.timer = setTimeout(() => {
          Alert.alert(msg);
          if (global.loggedInControllers) {
            this.props.navigation.navigate("ControllersScreen");
          }
        });
      }
    );
  }
}
const login = async (userName, password) => {
  var url =
    "Login" +
    "/" +
    global.DeviceId +
    "/" +
    userName +
    "/" +
    password +
    "/" +
    global.OS;
  try {
    const response = await yelp.post(`/${url}`);
    console.log(response.data);
    var data = response.data;
    global.loggedInControllers = data.Data;
    if (data.NotificationsCount) {
      global.NotificationCount = data.NotificationsCount;
    }
    _storeLoggedInUser(userName);
    _getLoggedInUser();

    updateState(data.ResponseMessage);
  } catch (e) {
    console.log(e);
    updateState(e);
  }
};

_storeLoggedInUser = async user => {
  try {
    await AsyncStorage.setItem("UserName", user);
  } catch (error) {
    console.log("Something went wrong", error);
  }
};
_getLoggedInUser = async () => {
  try {
    let name = await AsyncStorage.getItem("UserName");
    console.log("AFTER LOGIN " + name);
  } catch (error) {
    console.log("Something went wrong", error);
  }
};
class LoginScreen extends Component {
  constructor(props) {
    super(props);
    this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
    this.state = {
      visible: false,
      userName: "",
      password: "",
      errorMsg: ""
    };
    isMounted = false;
    updateState = updateState.bind(this);
  }

  componentDidMount() {
    const { navigation } = this.props;

    //Adding an event listner om focus
    //So whenever the screen will have focus it will set the state to zero
    this.focusListener = navigation.addListener("didFocus", () => {
      isMounted = true;
    });
  }

  componentWillMount() {
    BackHandler.addEventListener(
      "hardwareBackPress",
      this.handleBackButtonClick
    );
  }

  componentWillUnmount() {
    clearInterval(this.timer);
    this.focusListener.remove();
    BackHandler.removeEventListener(
      "hardwareBackPress",
      this.handleBackButtonClick
    );
    isMounted = false;
  }

  handleBackButtonClick() {
    this.props.navigation.goBack(null);
    return true;
  }

  state = {};

  validate(text) {
    var regx = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    if (!regx.test(text)) {
      return false;
    } else {
      return true;
    }
  }

  onClickBackToMainScreen() {
    this.props.navigation.navigate("MainScreen");
  }
  onClickLogin() {
    const { userName, password } = this.state;

    if (userName.trim() === "") {
      this.state.errorMsg = "Please enter username";
      alert(this.state.errorMsg);
    }
    // else if (!this.validate(userName)) {
    //   this.state.errorMsg = 'Please enter valid email';
    //   alert(this.state.errorMsg)
    // }
    else if (password.trim() === "") {
      this.state.errorMsg = "Please enter password";

      alert(this.state.errorMsg);
    } else {
      this.setState({
        visible: !this.state.visible
      });
      login(this.state.userName, this.state.password);
    }
  }

  render() {
    return (
      <ImageBackground source={IMAGELOGINBG} style={styles.bgContainer}>
        <KeyboardAwareScrollView
          style={styles.scrollContainer}
          contentContainerStyle={{ flexGrow: 1 }}
          innerRef={ref => {
            this.scroll = ref;
          }}
          onKeyboardWillShow={frames => {
            console.log("Keyboard event", frames);
          }}
        >
          <View style={styles.bigContainer}>
            <View style={styles.container}>
              <View style={styles.inputContainer}>
                <Image source={USERICON} style={styles.inputIconImage} />
                <TextInput
                  onFocus={event => {}}
                  allowFontScaling={false}
                  style={styles.editText}
                  placeholder="Username"
                  placeholderTextColor="gray"
                  underlineColorAndroid="transparent"
                  value={this.state.userName}
                  keyboardType="email-address"
                  onChangeText={userName => this.setState({ userName })}
                />
              </View>

              <View style={styles.inputContainer}>
                <Image source={PASSWORDICON} style={styles.inputIconImage} />
                <TextInput
                  allowFontScaling={false}
                  style={styles.editText}
                  placeholder="Password"
                  placeholderTextColor="gray"
                  underlineColorAndroid="transparent"
                  value={this.state.password}
                  onChangeText={password => this.setState({ password })}
                  secureTextEntry={true}
                />
              </View>

              <LinearGradient
                colors={["#870000", "#540000", "#2f0000"]}
                style={styles.buttonContainer}
                start={{ y: 0.0, x: 0.0 }}
                end={{ y: 0.0, x: 1.0 }}
              >
                <TouchableOpacity
                  style={styles.button}
                  onPress={this.onClickLogin.bind(this)}
                >
                  <Text allowFontScaling={false} style={styles.textButton}>
                    Login
                  </Text>
                </TouchableOpacity>
              </LinearGradient>

              <Text
                allowFontScaling={false}
                style={styles.textUnderline}
                onPress={this.onClickBackToMainScreen.bind(this)}
              >
                Back to scan controller
              </Text>
            </View>
          </View>
          {/* </ScrollView> */}
        </KeyboardAwareScrollView>
        <View style={{ height: 80, width: 80, justifyContent: "center" }}>
          <ProgressLoader
            visible={this.state.visible}
            isModal={true}
            isHUD={true}
            hudColor={"#fff"}
            color={"#000"}
          />
        </View>
      </ImageBackground>
    );
  }
}
var styles = StyleSheet.create({
  bgContainer: {
    width: "100%",
    height: "100%",
    justifyContent: "center",
    alignItems: "center"
  },

  bigContainer: {
    width: "100%",
    height: "100%",
    flex: 1,
    justifyContent: "center",
    alignItems: "center"
  },
  scrollContainer: {
    width: "100%",
    height: "100%",
    flex: 1
  },
  container: {
    height: 222,
    width: "80%",
    flexDirection: "column",
    alignItems: "center",
    position: "absolute", //Here is the trick
    bottom: 50 //Here is the trick
  },
  button: {
    height: "100%",
    width: "100%",

    // fontSize: 22,
    backgroundColor: "transparent"
  },
  buttonContainer: {
    height: 62,
    width: "100%",
    borderRadius: 30
  },
  inputContainer: {
    height: 60,
    width: "100%",
    borderRadius: 50,
    borderWidth: 1,
    borderColor: "#fff",
    marginBottom: 20,
    flexDirection: "row",
    alignItems: "center",
    backgroundColor: "#000",
    opacity: 0.7
  },
  editText: {
    height: "100%",
    color: "#fff",
    textAlignVertical: "top",
    fontSize: 20,
    width: "70%",
    backgroundColor: "#000",
    opacity: 0.7
  },
  bgImage: {
    flex: 1,
    width: undefined,
    height: undefined
  },
  inputIconImage: {
    width: "8%",
    height: 25,
    marginRight: "5%",
    resizeMode: "contain",
    marginLeft: "7%"
  },
  textUnderline: {
    textDecorationLine: "underline",
    textDecorationStyle: "solid",
    color: "#fff",
    marginTop: 10,
    paddingBottom: 3,
    fontSize: 18
  },
  textButton: {
    color: "#fff",
    fontSize: 16,
    textAlign: "center",
    textAlignVertical: "center",
    width: "100%",
    height: "100%",
    lineHeight: 62,
    alignSelf: "center"
  }
});
export default withUnmounted(LoginScreen);

【问题讨论】:

  • 否,componentWillUnmount 方法不会在某些屏幕中触发。
  • 您能否发布“LoginScreen”的完整代码?如果不了解发生了什么,就很难诊断问题。看起来这个组件正在侦听某种会改变其状态的事件,而当您离开屏幕时,它并没有被删除。
  • 使用登录屏幕代码编辑了我的问题

标签: react-native react-navigation setstate


【解决方案1】:

这可能与isMountedupdateState 有关。在组件内部和 updateState 函数本身中引用这两者时,您需要使用 this.isMountedthis.updateState

但是,isMounted 对于组件来说是一个有点奇怪的属性。如果组件未安装,则此属性不再存在,也不再存在您将用来检查它的方法。如果在未挂载组件时仍然调用updateState,那感觉就像是一个更深层次的问题。

无论如何,如果在对 isMountedupdateState 的每个引用前面添加 this. 是否有效,请告诉我

【讨论】:

  • 通过在 updateState 前面添加这个 >> YellowBox.js:67 可能的未处理的 Promise Rejection (id: 0): TypeError: _this2.updateState is not a function ..
  • 请问如何取消componentWillUnmount中的所有订阅和异步任务? ,, 不是第一次在我的屏幕上导航不止一次时发生内存泄漏问题,我使用 asyncStorage、setInterval、setTimout 和 axios 调用,你能告诉我如何在 componentWillUnmount 中取消所有这些吗?跨度>
猜你喜欢
  • 2021-11-30
  • 2020-09-03
  • 2022-01-18
  • 1970-01-01
  • 2019-09-11
  • 2021-08-07
  • 2020-06-14
  • 1970-01-01
  • 2021-07-01
相关资源
最近更新 更多