如何在本地仅渲染聊天的最后一条消息,并在 React Native 中使用动画?

问题描述 投票:0回答:1

我有一个聊天应用程序,当用户在第一次使用 useEffect() 运行时打开聊天窗口时,它会从 Firestore 获取所有消息,但是当用户发送新消息时,必须将其添加到本地状态以供更好的用户使用使用淡入动画而不是从数据库中返回和堡垒的本地时间戳的经验,现在它工作正常,但由于某种原因,发送的消息会显示并很快被删除并再次显示。我觉得它是从数据库而不是本地获取的。我怎样才能做到这一点?

```

const NewChatSection = ({ navigation, route }) => {
  const [messages, setMessages] = useState([]);
  const [message, setMessage] = useState("");
  const [lastMessage, setdddLastMessage] = useState(null);
  const scrollViewRef = useRef();
  const db = getFirestore();
  const auth = FIREBASE_AUTH;
  const { uid, displayName } = route.params;
  const userUIDs = [auth.currentUser.uid, uid].sort();
  const chatId = userUIDs.join("-");
  const chatRef = doc(db, "chats", chatId);
  const messagesRef = collection(db, "chats", chatId, "messages");
  const fadeAnim = useRef(new Animated.Value(0)).current;
  const fadeIn = () => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  if (Platform.OS === "android") {
    if (UIManager.setLayoutAnimationEnabledExperimental) {
      UIManager.setLayoutAnimationEnabledExperimental(true);
    }
  }

  const handleSend = async () => {
    if (message.trim() === "") {
      return;
    }

    const newMessage = {
      text: message,
      timestamp: new Date(), // This timestamp is for local display purposes only
      senderName: auth.currentUser.displayName,
      senderId: auth.currentUser.uid,
      receiverId: uid,
      receiverName: displayName,
      type: "sentMessage",
    };

    const newMessageForDb = {
      ...newMessage,
      timestamp: serverTimestamp(), // Use serverTimestamp for the database
    };

    // Optimistically update the local state with the new message
    setMessages((prevMessages) => [...prevMessages, newMessage]);

    try {
      await addDoc(messagesRef, newMessageForDb);
      setMessage("");
      fadeIn(); // Trigger the animation for the local message
    } catch (error) {
      console.error("Error sending message: ", error);
    }
  };

  useEffect(() => {
    const receiverUID = route.params?.uid;
    const currentUserUID = auth.currentUser.uid;
    const userUIDs = [currentUserUID, receiverUID].sort();
    const chatId = userUIDs.join("-");
    const messagesRef = collection(db, "chats", chatId, "messages");
    const q = query(messagesRef, orderBy("timestamp"));

    const unsubscribe = onSnapshot(q, (querySnapshot) => {
      const fetchedMessages = querySnapshot.docs.map((doc) => {
        let msg = doc.data();
        msg.type =
          msg.senderId === auth.currentUser.uid
            ? "sentMessage"
            : "receivedMessage";
        return msg;
      });
      setMessages(fetchedMessages);
      if (fetchedMessages.length > 0) {
        const lastMessage = fetchedMessages[fetchedMessages.length - 1];
        const users = {
          participants: [auth.currentUser.uid, uid].sort(),
          senderName: auth.currentUser.displayName,
          receiverName: displayName,
          lastMessage: lastMessage.text,
          lastMessageTimestamp: lastMessage.timestamp,
          deleted: false,
        };
        setDoc(chatRef, users, { merge: true });
      }
    });

    return () => unsubscribe();
  }, []);

  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === "ios" ? "padding" : "height"}
      style={styles.container}
      // animationType="slide"
    >
      <SafeAreaView style={styles.safeAreaContainer}>
        <View style={styles.header}>
          <TouchableOpacity
            onPress={() => navigation.goBack()}
            style={styles.closeButton}
          >
            <Svg
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke="#FFFFFF"
              width={30}
              height={30}
            >
              <Path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M15 19l-7-7 7-7"
              />
            </Svg>
          </TouchableOpacity>
          <Text style={styles.title}>{displayName}</Text>
        </View>
        <ScrollView
          style={styles.scrollViewContent}
          keyboardShouldPersistTaps="handled"
          ref={scrollViewRef}
          onContentSizeChange={() => {
            scrollViewRef.current.scrollToEnd({ animated: true });
          }}
        >
          {Array.isArray(messages) &&
            messages.map(
              (msg, index) => (
                console.log("MESSAGE: ", msg),
                (
                  <View
                    key={index}
                    style={[
                      msg.type === "sentMessage"
                        ? styles.messageContainer
                        : styles.messageContainerReceived,
                      {
                        flexDirection: "row",
                        justifyContent: "space-between",
                        alignItems: "center",
                      },
                    ]}
                  >
                    <Text style={styles.messageText}>{msg.text}</Text>
                    {msg.timestamp && (
                      <Text style={styles.messageTimestamp}>
                        {format(
                          msg.timestamp instanceof Date
                            ? msg.timestamp
                            : msg.timestamp.toDate(),
                          "p"
                        )}
                      </Text>
                    )}
                  </View>
                )
              )
            )}
/////////////////////////////////////COMMENTED OUT
          {/* {lastMessage && (
            <Animated.View
              style={[
                { opacity: fadeAnim },
                lastMessage.type === "sentMessage"
                  ? styles.messageContainer
                  : styles.messageContainerReceived,
                {
                  flexDirection: "row",
                  justifyContent: "space-between",
                  alignItems: "center",
                },
              ]}
            >
              <Text style={styles.messageText}>{lastMessage.text}</Text>
              <Text style={styles.messageTimestamp}>
                {format(new Date(), "p")}
              </Text>
            </Animated.View>
          )} */}
/////////////////////////////////COMMENTED OUT
        </ScrollView>

        <View style={styles.inputContainer}>
          <TextInput
            placeholder="Type your message..."
            style={styles.input}
            multiline
            value={message}
            onChangeText={setMessage}
            placeholderTextColor={"#ccc"}
          />

          <TouchableOpacity onPress={handleSend} style={styles.sendButton}>
            <Icon name="send" size={22} color="white" />
          </TouchableOpacity>
        </View>
      </SafeAreaView>
    </KeyboardAvoidingView>
  );
};

```

我尝试将所有消息与最后一条消息分开,但它不起作用,而且使它变得超级复杂,也许时间被说了两次,但在第一次渲染之后,屏幕上发生的所有其他事情都必须在本地完成.

javascript reactjs react-native google-cloud-firestore react-hooks
1个回答
0
投票

要解决这个问题,只有在消息成功发送到数据库后才能更新本地状态。以下是修改handleSend函数的方法:

const handleSend = async () => {
  if (message.trim() === "") {
    return;
  }

  const newMessage = {
    text: message,
    timestamp: new Date(), // This timestamp is for local display purposes only
    senderName: auth.currentUser.displayName,
    senderId: auth.currentUser.uid,
    receiverId: uid,
    receiverName: displayName,
    type: "sentMessage",
  };

  const newMessageForDb = {
    ...newMessage,
    timestamp: serverTimestamp(), // Use serverTimestamp for the database
  };

  try {
    await addDoc(messagesRef, newMessageForDb);
    setMessage("");
    // Note: Don't update the local state here, as the message will be updated in the snapshot listener below
  } catch (error) {
    console.error("Error sending message: ", error);
  }
};

然后,在您正在侦听来自数据库的新消息的 useEffect 挂钩中,仅当从数据库收到新消息时才更新本地状态:

useEffect(() => {
  // Other code...

  const unsubscribe = onSnapshot(q, (querySnapshot) => {
    const fetchedMessages = querySnapshot.docs.map((doc) => {
      let msg = doc.data();
      msg.type =
        msg.senderId === auth.currentUser.uid
          ? "sentMessage"
          : "receivedMessage";
      return msg;
    });
    setMessages(fetchedMessages);
    // Note: Don't update the last message here
  });

  return () => unsubscribe();
}, []);
© www.soinside.com 2019 - 2024. All rights reserved.