FrontendAdvanced
14 min readNov 24, 2025
Building a Realtime UI with Firestore Streams
Implement live updates, optimistic UI, and offline-first patterns with Firestore real-time listeners.
R
Rithy Tep
Author
Real-time Listeners
import { onSnapshot, collection } from 'firebase/firestore' function useRealtimeMessages(chatId: string) { const [messages, setMessages] = useState([]) useEffect(() => { const unsubscribe = onSnapshot( collection(db, 'chats', chatId, 'messages'), (snapshot) => { const newMessages = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) setMessages(newMessages) } ) return () => unsubscribe() }, [chatId]) return messages }
Optimistic Updates
async function sendMessage(text: string) { const tempId = `temp-${Date.now()}` // Optimistic update setMessages(prev => [...prev, { id: tempId, text, sending: true, createdAt: new Date() }]) try { const docRef = await addDoc(collection(db, 'messages'), { text, userId: auth.currentUser.uid, createdAt: serverTimestamp() }) // Replace temp message with real one setMessages(prev => prev.map(msg => msg.id === tempId ? { ...msg, id: docRef.id, sending: false } : msg) ) } catch (error) { // Revert on error setMessages(prev => prev.filter(msg => msg.id !== tempId)) showError('Failed to send message') } }
Offline-First Pattern
import { enableIndexedDbPersistence } from 'firebase/firestore' // Enable offline persistence enableIndexedDbPersistence(db).catch((err) => { if (err.code === 'failed-precondition') { console.warn('Multiple tabs open') } else if (err.code === 'unimplemented') { console.warn('Browser doesn't support offline') } }) // Data syncs automatically when back online
Handling Connection State
import { onDisconnect, ref, set } from 'firebase/database' function usePresence(userId: string) { useEffect(() => { const userStatusRef = ref(rtdb, `/status/${userId}`) onDisconnect(userStatusRef).set({ state: 'offline', lastSeen: Date.now() }) set(userStatusRef, { state: 'online', lastSeen: Date.now() }) }, [userId]) }
Performance Tips
- •Use
limit()to avoid loading too much data - •Implement pagination with
startAfter() - •Cache aggressively with persistence
- •Show loading skeletons during initial load
#Firestore#Real-time#React#Vue#Offline-First