I'm using firebase Firestore to store my chat messages in it ! everything works fine but the only problem I'm facing is that messages are not showing directly after sending them ! I used useEffect and useLayoutEffect but still nothing works ! maybe I'm not using the right dependencies
I tried using messages as dependency so after the messages get a new message it re-renders ! but still not working ?
const ChatScreen = ({ navigation, route, ...props }) => {
const { id, chatName } = route.params;
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
useLayoutEffect(() => {
const fetchMessages = async () => {
const messagesSnapshop = await getDocs(
query(
collection(db, "chats", id, "messages"),
orderBy("timestamp", "desc")
)
);
setMessages(
messagesSnapshop.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
);
};
fetchMessages();
}, []);
const sendMessage = async () => {
Keyboard.dismiss();
if (input !== "") {
const docRef = doc(db, "chats", id);
const colRef = collection(docRef, "messages");
addDoc(colRef, {
message: input,
timestamp: serverTimestamp(),
displayName: authentication.currentUser.displayName,
email: authentication.currentUser.email,
photoURL: authentication.currentUser.photoURL,
});
}
};
useLayoutEffect(() => {
navigation.setOptions({
title: chatName,
headerBackTitle: "Chats",
headerStyle: { backgroundColor: "#2B68E6" },
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
headerRight: () => (
<View
style={{
flexDirection: "row",
marginRight: 15,
}}
>
<View style={{ marginRight: 20 }}>
<Ionicons name="call-outline" size={24} color="white"></Ionicons>
</View>
<View>
<Ionicons
name="videocam-outline"
size={24}
color="white"
></Ionicons>
</View>
</View>
),
});
}, []);
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "white" }}>
<KeyboardAvoidingView
behavior={Platform.OS === "android" ? "height" : "padding"}
style={styles.container}
keyboardVerticalOffset={90}
>
<>
<ScrollView contentContainerStyle={{ paddingTop: 15 }}>
{messages.map(({ id, data }) =>
data.email === authentication.currentUser.email ? (
<View key={id} style={styles.sender}>
<Avatar
rounded
size={30}
right={-5}
bottom={-15}
position="absolute"
source={{ uri: data.photoURL }}
/>
{console.log("DATA", data)}
<Text style={styles.senderText}>{data.message}</Text>
<Text style={styles.senderName}>{data.displayName}</Text>
</View>
) : (
<View key={id} style={styles.receiver}>
<Avatar
rounded
size={30}
right={-5}
bottom={-15}
position="absolute"
source={{ uri: data.photoURL }}
/>
<Text style={styles.receiverText}>{data.message}</Text>
<Text style={styles.receiverName}>{data.displayName}</Text>
</View>
)
)}
</ScrollView>
<View style={styles.footer}>
<TextInput
value={input}
placeholder="Write message here!"
style={styles.textInput}
onChangeText={(text) => {
setInput(text);
}}
/>
<TouchableOpacity onPress={sendMessage}>
<Ionicons name="send" size={24} color="#2B68E6" />
</TouchableOpacity>
</View>
</>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
CodePudding user response:
getDocs
fetches data once and is done. You could just run the fetchMessages
function again after sending a message. Note that this isn't a very proactive solution and only fetches messages once when the component mounts and only when this client sends a message.
Example:
const ChatScreen = ({ navigation, route, ...props }) => {
const { id, chatName } = route.params;
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
const fetchMessages = async () => {
const messagesSnapshop = await getDocs(
query(
collection(db, "chats", id, "messages"),
orderBy("timestamp", "desc")
)
);
setMessages(
messagesSnapshop.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
);
};
useLayoutEffect(() => {
fetchMessages();
}, []);
const sendMessage = async () => {
Keyboard.dismiss();
if (input !== "") {
const docRef = doc(db, "chats", id);
const colRef = collection(docRef, "messages");
await addDoc(colRef, {
message: input,
timestamp: serverTimestamp(),
displayName: authentication.currentUser.displayName,
email: authentication.currentUser.email,
photoURL: authentication.currentUser.photoURL,
});
fetchMessages();
}
};
...
A suggested solution is to subscribe to the collection with a snapshot listener. This will receive changes to the collection when the collection is updated, not just when this client sends a message.
Example:
import { addDoc, collection, query, orderBy, onSnapshot } from "firebase/firestore";
const ChatScreen = ({ navigation, route, ...props }) => {
const { id, chatName } = route.params;
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
useLayoutEffect(() => {
const unsubscribe = onSnapshot(
query(
collection(db, "chats", id, "messages"),
orderBy("timestamp", "desc")
),
(querySnapshot) => {
const messages = [];
querySnapshot.forEach((doc) => {
cities.push({ id: doc.id, data: doc.data() });
});
setMessages(messages);
}
);
return unsubscribe;
}, []);
const sendMessage = async () => {
Keyboard.dismiss();
if (input !== "") {
const docRef = doc(db, "chats", id);
const colRef = collection(docRef, "messages");
addDoc(colRef, {
message: input,
timestamp: serverTimestamp(),
displayName: authentication.currentUser.displayName,
email: authentication.currentUser.email,
photoURL: authentication.currentUser.photoURL,
});
}
};
...