Ad

Getting "Can't Perform A React State Update On An Unmounted Component" When Switching Between Screens

When switching between the Home.js and Chat.js files, I get this warning: "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 the componentWillUnmount method". I removed the only listener that's on Chat.js and tried only setting state when the component is mounted in Home.js and removing it on unmount but I still get this warning.

Home.js

import React, { Component } from "react";
import { View, FlatList } from "react-native";
import { ListItem } from "react-native-elements";
import fireStoreDB from "../database/FirestoreDB";

let _isMounted = false;

export default class Home extends Component {
  constructor(props) {
    super(props);
    this.state = {
      usersInfo: [],
      refreshing: false
    };
  }

  componentDidMount() {
    _isMounted = true;
    this.LoadUsers();
  }

  componentWillUnmount() {
    _isMounted = false;
  }

  LoadUsers = () => {
    fireStoreDB
      .getAllUsersExceptCurrent()
      .then(users =>
        Promise.all(
          users.map(({ id, username, avatar }) =>
            fireStoreDB
              .getUserLastMessage(fireStoreDB.getUID, id)
              .then(message => ({ id, username, avatar, message }))
          )
        )
      )
      .then(users => {
        if (_isMounted) {
          this.setState({
            usersInfo: users.filter(x => typeof x.avatar !== "undefined"),
            refreshing: false
          });
        }
      });
  };

  renderItem = ({ item }) => (
    <ListItem
      onPress={() => {
        this.props.navigation.navigate("Chat", {
          userTo: item.id,
          UserToUsername: item.username,
          LoadUsers: this.LoadUsers
        });
      }}
      title={item.username}
      subtitle={item.message}
      leftAvatar={{ source: { uri: item.avatar } }}
      bottomDivider
      chevron
    />
  );

  render() {
    return (
      <View>
        <FlatList
          data={this.state.usersInfo}
          renderItem={this.renderItem}
          keyExtractor={item => item.id}
          refreshing={this.state.refreshing}
          onRefresh={() => {
            this.setState({ refreshing: true });
            this.LoadUsers();
          }}
        />
      </View>
    );
  }
}

Chat.js

import React, { Component } from "react";
import { View, KeyboardAvoidingView } from "react-native";
import { HeaderBackButton } from "react-navigation-stack";
import { GiftedChat } from "react-native-gifted-chat";
import * as Progress from "react-native-progress";
import fireStoreDB from "../database/FirestoreDB";

const Themes = {
  primaryTheme: "#30D921",
  secondaryTheme: "#B32D83",
  layoutTheme: "#c0c0c0"
};

export default class Chat extends Component {
  static navigationOptions = ({ navigation }) => ({
    title: navigation.getParam("UserToUsername"),
    headerLeft: (
      <HeaderBackButton
        onPress={() => {
          navigation.state.params.LoadUsers();
          navigation.goBack();
        }}
      />
    )
  });

  constructor(props) {
    super(props);
    this.state = {
      messages: [],
      userToId: this.props.navigation.getParam("userTo")
    };
  }

  componentDidMount() {
    fireStoreDB.getMessages(
      message =>
        this.setState(previousState => ({
          messages: GiftedChat.append(previousState.messages, message)
        })),
      this.chatId
    );
  }

  componentWillUnmount() {
    fireStoreDB.removeSnapshotListener(this.chatId);
  }

  // gifted chat user props
  get user() {
    return {
      _id: fireStoreDB.getUID,
      name: fireStoreDB.getName,
      avatar: fireStoreDB.getAvatar
    };
  }

  // merge ids between two parties for one to one chat
  get chatId() {
    const userFromId = fireStoreDB.getUID;
    const chatIdArray = [];
    chatIdArray.push(userFromId);
    chatIdArray.push(this.state.userToId);
    chatIdArray.sort(); // prevents other party from recreating key
    return chatIdArray.join("_");
  }

  render() {
    if (this.state.messages.length === 0) {
      return (
        <View
          style={{
            alignItems: "center",
            marginTop: 260
          }}
        >
          <Progress.Bar indeterminate color={Themes.primaryTheme} />
        </View>
      );
    }
    return (
      <View style={{ flex: 1 }}>
        <GiftedChat
          messages={this.state.messages}
          onSend={messages => fireStoreDB.sendMessages(messages, this.chatId)}
          user={this.user}
        />
        <KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={80} />
      </View>
    );
  }
}

FirestoreDB.js

  removeSnapshotListener = chatId => {
    firebase
      .firestore()
      .collection("messages")
      .doc(chatId)
      .collection("chats")
      .orderBy("createdAt")
      .onSnapshot(() => {});
  };
Ad

Answer

UPDATE:

With your implementation, you cannot unsubribe messages collection. You could try to return unsubscribe function from getMessages, then use it in componentWillUnmount

FirestoreDB.js

getMessages = (callback, chatId) => {
  return firebase
      .firestore()
      .collection("messages")
      .doc(chatId)
      .collection("chats")
      .orderBy("createdAt")
      .onSnapshot(callback);
}

Chat.js

  componentDidMount() {
    this.unsubcribe = fireStoreDB.getMessages(
      message =>
        this.setState(previousState => ({
          messages: GiftedChat.append(previousState.messages, message)
        })),
      this.chatId
    );
  }

  componentWillUnmount() {
     this.unsubcribe();
  }
Ad
source: stackoverflow.com
Ad