'React native randomly crashes with error 'Attempted to remove more RCTKeyboardObserver listeners than added'

My react native project on iOS simulator crashes randomly with this error when I open and close the ChatRoom screen, I guess it's a problem with the react native gifted chat library I'm not sure, that's the only external library I use in that screen.

I have tried Keyboard remove listener solution Keyboard.addListener("keyboardWillShow").remove(); from https://github.com/rgommezz/react-native-offline/issues/55 in my App.js file but it don't work.

I have added the code of the screen where this crash randomly occurs. Project versions are "react": "17.0.2", "react-native": "0.66.4", "react-native-gifted-chat": "^0.16.3".

ChatRoom

const ChatRoom = ({ navigation, route }) => {
  React.useLayoutEffect(() => {
    navigation.setOptions({
      title: name,
    });
  }, [navigation, name]);

  React.useEffect(() => {
    mounted.current = true;
    async function fetch() {
      const response = await getUserData(receiverId);
      if (mounted.current) {
        setChatImage(response.userimg);
        setName(response.name);
      }

      const chat = await getChat(user.uid, userid);

      if (chat) {
        const unsubscribe = firestore()
          .collection("Messages")
          .where("chatid", "==", chat)
          .onSnapshot((querySnapshot) => {
            const messages = querySnapshot
              .docChanges()
              .filter(({ type }) => type === "added")
              .map(({ doc }) => {
                const message = doc.data();
                return {
                  ...message,
                  createdAt: message.createdAt.toDate(),
                };
              })
              .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
            appendMessages(messages);
          });

        return () => unsubscribe();
      }
    }
    fetch();
    return () => (mounted.current = false);
  }, []);

  React.useEffect(() => {
    // set message state
    if (receiverId) {
      if (user.uid !== receiverId) {
        async function fetch() {
          const chat = await getChat(user.uid, userid);
          await markMsgStatus(user.uid, chat);
        }
        fetch();
      }
    }
  }, [receiverId]);

  const appendMessages = React.useCallback(
    (messages) => {
      setMessages((previousMessages) =>
        GiftedChat.append(previousMessages, messages)
      );
    },
    [messages]
  );

  const handleSend = async (messages) => {
    // if there are no chats, create one
    const chat = await getChat(user.uid, userid);
    let chatid;
    if (!chat) {
      chatid = await createChat(user.uid, userid);
    }

    // increment the no of messages in the chat by one
    firestore()
      .collection("Chats")
      .doc(chat ? chat : chatid)
      .set(
        {
          noofmsgs: firestore.FieldValue.increment(1),
        },
        { merge: true }
      )
      .then(() => console.log("message count on chat increased by one"))
      .catch((e) => console.log(e));

    // add the messages
    const writes = messages.map((m) =>
      firestore()
        .collection("Messages")
        .add({
          ...m,
          sent: true,
          received: false,
          senderid: user.uid,
          receiverid: userid,
          chatid: chat ? chat : chatid,
        })
    );
    await Promise.all(writes);

    // add the latest msg to chat
    const writetochat = messages.find((m) =>
      firestore()
        .collection("Chats")
        .doc(chat ? chat : chatid)
        .set(
          {
            latestmsg: m,
            latestmsgsender: user.uid,
            latestmsgreceiver: userid,
          },
          { merge: true }
        )
    );
    await Promise.all(writetochat);
  };

  return (
    <View>
      <GiftedChat
        messages={messages}
        user={{
          _id: user.uid,
        }}
        onSend={handleSend}
        showAvatarForEveryMessage={true}
      />
    </View>
  );
};

export default ChatRoom;



Solution 1:[1]

Having the same issue and a kinda crash on ios when unmounting the component I was not able to make AkShil's solution work. I found this tutorial which shows a similar solution.

For me it worked like this :

  1. npm install patch-package -D
  2. Change the root package.json script section and add "postinstall": "patch-package"
  3. Change the file node_modules/react-native-gifted-chat/lib/MessageContainer.js according to AkShil's solution
  4. npx patch-package react-native-gifted-chat

This creates a folder /patches in the root directory and updates automatically the node_modules' packages when you npm install. Works like charm.

PS: to ios simulator users just check and uncheck I/O -> Keyboard if it does not show anymore

Solution 2:[2]

This is the issue of the package itself. Below is how I fixed it.

  1. Install package npm i patch-package
  2. Create a Folder called patches on the root of the directory
  3. Create a new file name patch-rn-gifted-chat within patch folder
  4. add below code to it
diff --git a/node_modules/react-native-gifted-chat/lib/MessageContainer.js b/node_modules/react-native-gifted-chat/lib/MessageContainer.js
index 193772a..4e80378 100644
--- a/node_modules/react-native-gifted-chat/lib/MessageContainer.js
+++ b/node_modules/react-native-gifted-chat/lib/MessageContainer.js
@@ -55,18 +55,18 @@ export default class MessageContainer extends React.PureComponent {
         this.attachKeyboardListeners = () => {
             const { invertibleScrollViewProps: invertibleProps } = this.props;
             if (invertibleProps) {
-                Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
-                Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
-                Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
-                Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
+                this.willShowSub = Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
+                this.didShowSub = Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
+                this.willHideSub = Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
+                this.didHideSub = Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
             }
         };
         this.detachKeyboardListeners = () => {
             const { invertibleScrollViewProps: invertibleProps } = this.props;
-            Keyboard.removeListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
-            Keyboard.removeListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
-            Keyboard.removeListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
-            Keyboard.removeListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
+            this.willShowSub?.remove();
+            this.didShowSub?.remove();
+            this.willHideSub?.remove();
+            this.didHideSub?.remove();
         };
         this.renderTypingIndicator = () => {
             if (Platform.OS === 'web') {

Run command yarn or npm i

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JeanJacquesGourdin
Solution 2 Akshil Shah