0
0
FirebaseHow-ToBeginner · 3 min read

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

MethodPurpose
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.