I have a chat app and it fetches all the messages from Firestore when user opens a chat window at the first run with useEffect(), but when a new message is getting sent by the user it must get added to local state for better user experience with local timeStamp with a fade-in animation instead of back and fort from database, , right now it works fine but for some reason the sent messages gets displayed and gets removed very quickly and gets displayed again. I feel like it’s fetching from the database instead of locally. how can I make this work?
“`
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>
);
};
“`
I tried separating all the messages from the last message, but it didn’t work and it was making it super complicated, maybe the time is getting said twice, but after the first render everything else that’s happening on the screen must get done locally.