How to Use Transaction in Firestore: Simple Guide
Use
runTransaction in Firestore to perform multiple read and write operations atomically. Inside the transaction function, read documents with transaction.get() and write with transaction.set(), transaction.update(), or transaction.delete(). This ensures all operations succeed or fail together.Syntax
The runTransaction method takes a function that receives a transaction object. Use transaction.get(docRef) to read a document, and transaction.set(docRef, data), transaction.update(docRef, data), or transaction.delete(docRef) to write changes. The function must return a value or a promise.
javascript
db.runTransaction(async (transaction) => { const doc = await transaction.get(docRef); if (!doc.exists) { throw 'Document does not exist!'; } const newValue = doc.data().count + 1; transaction.update(docRef, { count: newValue }); return newValue; }).then(result => { console.log('Transaction success, new count:', result); }).catch(error => { console.log('Transaction failure:', error); });
Example
This example shows how to safely increment a counter in a Firestore document using a transaction. It reads the current count, adds one, and updates the document atomically.
javascript
import { getFirestore, doc, runTransaction } from 'firebase/firestore'; const db = getFirestore(); const counterRef = doc(db, 'counters', 'myCounter'); async function incrementCounter() { try { const newCount = await runTransaction(db, async (transaction) => { const docSnap = await transaction.get(counterRef); if (!docSnap.exists()) { throw 'Document does not exist!'; } const currentCount = docSnap.data().count || 0; const updatedCount = currentCount + 1; transaction.update(counterRef, { count: updatedCount }); return updatedCount; }); console.log('Transaction success: new count is', newCount); } catch (e) { console.error('Transaction failed:', e); } } incrementCounter();
Output
Transaction success: new count is 6
Common Pitfalls
- Not returning a value or promise inside the transaction function can cause unexpected behavior.
- Trying to read or write outside the transaction function breaks atomicity.
- Ignoring document existence checks can cause errors.
- Using transactions for long-running operations can lead to contention and retries.
javascript
/* Wrong: Not returning a value */ db.runTransaction(async (transaction) => { const doc = await transaction.get(docRef); transaction.update(docRef, { count: (doc.data().count || 0) + 1 }); // Missing return statement }); /* Right: Return the new value */ db.runTransaction(async (transaction) => { const doc = await transaction.get(docRef); const newCount = (doc.data().count || 0) + 1; transaction.update(docRef, { count: newCount }); return newCount; });
Quick Reference
| Method | Purpose |
|---|---|
| runTransaction(db, updateFunction) | Starts a transaction and runs the updateFunction atomically |
| transaction.get(docRef) | Reads a document snapshot inside the transaction |
| transaction.set(docRef, data) | Sets data to a document inside the transaction |
| transaction.update(docRef, data) | Updates fields of a document inside the transaction |
| transaction.delete(docRef) | Deletes a document inside the transaction |
Key Takeaways
Use runTransaction to perform atomic read and write operations in Firestore.
Always return a value or promise inside the transaction function.
Check if documents exist before updating them in a transaction.
Keep transactions short to avoid contention and retries.
Use transaction methods only inside the transaction function for atomicity.