Skip to content

Firestore Real-Time Listeners — Live Data Updates with onSnapshot

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Firestore Real. We cover key concepts, practical examples, and best practices to help you master this topic.

Firestore real-time listeners use onSnapshot to subscribe to document or query changes, receiving immediate updates when data changes without polling the server.

What You'll Learn

  • Setting up onSnapshot listeners for documents and collections
  • Handling document changes, metadata, and errors
  • Detaching listeners and managing subscriptions

Why It Matters

Real-time updates power collaborative and live-data applications. Without onSnapshot, you would need constant polling. DodaTech's real-time threat monitoring dashboard uses Firestore listeners for instant alert display.

sequenceDiagram
    Client->>Firestore: onSnapshot subscription
    Firestore-->>Client: Initial snapshot
    Note over Client: Display initial data
    Client->>Firestore: User makes change
    Firestore-->>Client: Updated snapshot
    Note over Client: Update UI immediately
    Client->>Firestore: Detach listener
    Firestore-->>Client: Subscription ended

Code Examples

// Document listener
import { doc, onSnapshot } from 'firebase/firestore';

const unsubscribe = onSnapshot(
  doc(db, 'users', 'alice@example.com'),
  (doc) => {
    if (doc.exists()) {
      console.log('User data:', doc.data());
    } else {
      console.log('User document deleted');
    }
  },
  (error) => {
    console.error('Listener error:', error);
  }
);

// Later: detach the listener
unsubscribe();
// Collection query listener
import { collection, query, where, onSnapshot } from 'firebase/firestore';

const q = query(
  collection(db, 'alerts'),
  where('severity', '==', 'critical'),
  where('resolved', '==', false)
);

const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === 'added') {
      console.log('New alert:', change.doc.data());
      showNotification(change.doc.data());
    }
    if (change.type === 'modified') {
      console.log('Alert updated:', change.doc.data());
      updateAlertInUI(change.doc.id, change.doc.data());
    }
    if (change.type === 'removed') {
      console.log('Alert removed:', change.doc.data());
      removeAlertFromUI(change.doc.id);
    }
  });
});

// Include metadata
onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => {
  const isFromCache = snapshot.metadata.fromCache;
  const hasPending = snapshot.metadata.hasPendingWrites;
  console.log('Source:', isFromCache ? 'cache' : 'server');
  console.log('Pending writes:', hasPending);
});
# Python Firestore listener
from google.cloud import firestore

db = firestore.Client()
doc_ref = db.collection('alerts').document('alert-123')

# Watch a document
watcher = doc_ref.on_snapshot(
    lambda docs, changes, read_time: print(
        f'Document changed: {[d.to_dict() for d in docs]}'
    )
)

# Watch a query
query = (
    db.collection('alerts')
    .where('severity', '==', 'critical')
    .where('resolved', '==', False)
)

query_watcher = query.on_snapshot(
    lambda docs, changes, read_time: print(
        f'Query changed: {len(changes)} changes'
    )
)

Common Mistakes

1. Not Detaching Listeners When Component Unmounts

Undetached listeners cause memory leaks and unnecessary read charges.

2. Ignoring the Error Callback

Network errors or permission changes can cause listeners to fail silently.

3. Using Too Many Listeners

Each listener counts as a read. Combine queries when possible.

4. Not Handling Metadata Changes

IncludeMetadataChanges prevents UI flicker during cache vs server transitions.

5. Mutating State Directly in Listener Callbacks

Always create copies of document data before updating application state.

Practice Questions

  1. How do you subscribe to real-time changes on a Firestore document?
  2. How do you detach a Firestore listener?
  3. What does docChanges() return?
  4. Why include metadata changes?
  5. What happens when a listener encounters a permission error?

Answers:

  1. Call onSnapshot() with the document reference and a callback function.
  2. Call the unsubscribe function returned by onSnapshot().
  3. An array of change objects with type (added/modified/removed) and the document data.
  4. To detect when data comes from cache vs server and when there are pending local writes.
  5. The error callback fires with a FirestoreError. The listener is not automatically detached.

Challenge: Build a real-time collaborative todo list where multiple users can add, complete, and delete tasks. Use onSnapshot for instant updates across all clients. Handle offline changes and Conflict Resolution.

FAQ

How many listeners can I have on a single document?

There is no hard limit, but each listener counts as a read. Use a single listener per component and share data via state management.

Do Firestore listeners work offline?

Yes. Local changes are emitted immediately via the listener with hasPendingWrites=true. Server updates arrive when connectivity is restored.

What is the difference between onSnapshot and getDocs?

onSnapshot subscribes to real-time updates. getDocs fetches data once. onSnapshot is for live data, getDocs for static data.

How do I handle listener errors gracefully?

Provide an error callback that shows a user-friendly message and optionally retries the subscription with exponential backoff.

Can I use onSnapshot with Firestore emulators?

Yes. Local emulators support real-time listeners with the same API as production Firestore.

Mini Project

Build a real-time monitoring dashboard that displays Firestore document changes as they happen. Show added, modified, and removed documents with color-coded indicators. Include a log panel showing each change event and a filter for specific document types.

What's Next

Learn about batch writes for efficient bulk operations, then explore Firestore transactions for atomic data updates.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro