Reputation: 441
I am creating a chat app using firestore where I use Flatlist and querying using onSnapshot() to show both sender and receiver realtime
my sample query below is like this:
const ChatMessages = useCallback(() => {
privateMessage
.doc(chatId)
.collection('messages')
.orderBy('createdAt', 'asc')
.onSnapshot((querySnapshot) => {
const messageList = [];
querySnapshot.forEach((doc) => {
messageList.push(doc.data());
})
setMessages(messageList);
}, error => {
console.log(error)
})
}, [chatId])
then in my sample flatlist I have the user display name, message, and the time created that will be displayed on the screen:
<FlatList
data={messages}
keyExtractor={(item, index) => String(index)}
removeClippedSubviews={false}
renderItem={({ item }) => (
<View>
<Text style={chatstyle.pmdate}>
{item.createdAt.toDate().toDateString()}
</Text>
<Text>
{item.displayName}
</Text>
<Text>
{item.message}
</Text>
</View>
)}
/>
I would like to display the time and date but when using onSnapshot() I get null is not an object (evaluating 'item.createdAt.toDate') error but when I removed {item.createdAt.toDate().toDateString()} everything is ok. I tried the get() query and the timestamp works fine but of course it's not realtime.
What is the right way to display the timestamp using onSnapshot()?
Upvotes: 0
Views: 847
Reputation: 1169
I'm new to React as well, but for those who are looking a quick solution & couldn't figure out a way to do it , here is one :
const ChatMessages = useCallback(() => {
privateMessage
.doc(chatId)
.collection('messages')
.orderBy('createdAt', 'asc')
.onSnapshot((querySnapshot) => {
const messageList = [];
querySnapshot.forEach((doc) => {
if(querySnapshot.createdAt) // Here is the magic Line of Code
messageList.push(doc.data());
})
setMessages(messageList);
}, error => {
console.log(error)
})
}, [chatId])
Yes that's it. A single if statement which checks for the createdAt attribute in the data & then only pushes the data to your array.
If you are thinking about the drawbacks of this solution, then here is it :
time for the if() to validate is extra in your program.
PS : I tried the current approved solution on my react application, but still the null issue on the timeStamp was happening.
Upvotes: 2
Reputation: 83058
Your problem comes from the "latency compensation": "Local writes in your app will invoke snapshot listeners immediately. ... When you perform a write, your listeners will be notified with the new data before the data is sent to the backend". See the doc on onSnapshot()
.
Since you use firebase.firestore.FieldValue.serverTimestamp()
to set the value of createdAt
(which is a good approach), the value of createdAt
is calculated by the backend (the serverTimestamp
sentinel is replaced by a server-generated timestamp in the written data).
Therefore, at the moment the snapshot listener is invoked in your front-end following the local write ("Local writes in your app will invoke snapshot listeners immediately"), this value is not set (item.createdAt.toDate()
generates an error).
One solution is to use the metadata.hasPendingWrites
property that indicates whether the document has local changes that haven't been written to the backend yet.
For example:
const ChatMessages = useCallback(() => {
privateMessage
.doc(chatId)
.collection('messages')
.orderBy('createdAt', 'asc')
.onSnapshot((querySnapshot) => {
const messageList = [];
querySnapshot.forEach((doc) => {
messageList.push(doc.data());
})
if (!querySnapshot.metadata.hasPendingWrites) { // <======
setMessages(messageList);
}
}, error => {
console.log(error)
})
}, [chatId])
Upvotes: 2