CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-native-firebase--firestore

React Native Firebase Cloud Firestore provides a NoSQL document database with real-time synchronization capabilities.

Pending
Overview
Eval results
Files

transactions-batches.mddocs/

Transactions & Batches

Atomic operations for ensuring data consistency across multiple operations. Supports both read-write transactions and write-only batches.

Capabilities

Transactions

Execute multiple operations atomically with read and write capabilities. Transactions ensure data consistency by retrying operations if data changes during execution.

/**
 * Run a transaction with automatic retries
 * @param updateFunction - Function that performs transaction operations
 * @returns Promise resolving to the transaction result
 */
runTransaction<T>(updateFunction: (transaction: FirebaseFirestoreTypes.Transaction) => Promise<T>): Promise<T>;

interface Transaction {
  /**
   * Read a document within the transaction
   * @param documentRef - Document reference to read
   * @returns Promise resolving to document snapshot
   */
  get<T>(documentRef: FirebaseFirestoreTypes.DocumentReference<T>): Promise<FirebaseFirestoreTypes.DocumentSnapshot<T>>;

  /**
   * Set document data within the transaction
   * @param documentRef - Document reference to set
   * @param data - Document data to set
   * @param options - Optional set options for merging
   * @returns Transaction instance for chaining
   */
  set<T>(
    documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
    data: T,
    options?: FirebaseFirestoreTypes.SetOptions
  ): FirebaseFirestoreTypes.Transaction;

  /**
   * Update document fields within the transaction
   * @param documentRef - Document reference to update
   * @param data - Partial data to update
   * @returns Transaction instance for chaining
   */
  update<T>(
    documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
    data: Partial<T>
  ): FirebaseFirestoreTypes.Transaction;

  /**
   * Update specific fields using field paths within the transaction
   * @param documentRef - Document reference to update
   * @param field - Field path to update
   * @param value - New field value
   * @param moreFieldsAndValues - Additional field-value pairs
   * @returns Transaction instance for chaining
   */
  update<T>(
    documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
    field: keyof T | FirebaseFirestoreTypes.FieldPath,
    value: any,
    ...moreFieldsAndValues: any[]
  ): FirebaseFirestoreTypes.Transaction;

  /**
   * Delete a document within the transaction
   * @param documentRef - Document reference to delete
   * @returns Transaction instance for chaining
   */
  delete(documentRef: FirebaseFirestoreTypes.DocumentReference): FirebaseFirestoreTypes.Transaction;
}

Usage Examples:

import firestore from '@react-native-firebase/firestore';

// Transfer money between accounts atomically
async function transferMoney(fromAccountId: string, toAccountId: string, amount: number) {
  const db = firestore();
  
  return db.runTransaction(async (transaction) => {
    // Read current balances
    const fromAccountRef = db.collection('accounts').doc(fromAccountId);
    const toAccountRef = db.collection('accounts').doc(toAccountId);
    
    const fromAccountDoc = await transaction.get(fromAccountRef);
    const toAccountDoc = await transaction.get(toAccountRef);
    
    if (!fromAccountDoc.exists || !toAccountDoc.exists) {
      throw new Error('One or both accounts do not exist');
    }
    
    const fromBalance = fromAccountDoc.data().balance;
    const toBalance = toAccountDoc.data().balance;
    
    if (fromBalance < amount) {
      throw new Error('Insufficient funds');
    }
    
    // Perform the transfer
    transaction.update(fromAccountRef, {
      balance: fromBalance - amount,
      lastTransactionAt: firestore.FieldValue.serverTimestamp()
    });
    
    transaction.update(toAccountRef, {
      balance: toBalance + amount,
      lastTransactionAt: firestore.FieldValue.serverTimestamp()
    });
    
    // Create transaction record
    const transactionRef = db.collection('transactions').doc();
    transaction.set(transactionRef, {
      fromAccountId,
      toAccountId,
      amount,
      createdAt: firestore.FieldValue.serverTimestamp(),
      type: 'transfer'
    });
    
    return { transactionId: transactionRef.id, newFromBalance: fromBalance - amount };
  });
}

// Increment counter with collision handling
async function incrementCounter(counterId: string) {
  const db = firestore();
  
  return db.runTransaction(async (transaction) => {
    const counterRef = db.collection('counters').doc(counterId);
    const counterDoc = await transaction.get(counterRef);
    
    if (!counterDoc.exists) {
      // Create new counter
      transaction.set(counterRef, { count: 1 });
      return 1;
    } else {
      // Increment existing counter
      const newCount = counterDoc.data().count + 1;
      transaction.update(counterRef, { count: newCount });
      return newCount;
    }
  });
}

// Complex transaction with multiple operations
async function createOrderWithInventoryCheck(orderData: any) {
  const db = firestore();
  
  return db.runTransaction(async (transaction) => {
    const results = [];
    
    // Check inventory for all items
    for (const item of orderData.items) {
      const productRef = db.collection('products').doc(item.productId);
      const productDoc = await transaction.get(productRef);
      
      if (!productDoc.exists) {
        throw new Error(`Product ${item.productId} not found`);
      }
      
      const availableStock = productDoc.data().stock;
      if (availableStock < item.quantity) {
        throw new Error(`Insufficient stock for product ${item.productId}`);
      }
      
      results.push({ productRef, currentStock: availableStock, orderQuantity: item.quantity });
    }
    
    // Update inventory
    results.forEach(({ productRef, currentStock, orderQuantity }) => {
      transaction.update(productRef, {
        stock: currentStock - orderQuantity,
        lastOrderAt: firestore.FieldValue.serverTimestamp()
      });
    });
    
    // Create order
    const orderRef = db.collection('orders').doc();
    transaction.set(orderRef, {
      ...orderData,
      status: 'confirmed',
      createdAt: firestore.FieldValue.serverTimestamp()
    });
    
    return { orderId: orderRef.id };
  });
}

Write Batches

Execute multiple write operations atomically without reads. Batches are more efficient than transactions when you only need to write data.

/**
 * Create a new write batch
 * @returns WriteBatch instance
 */
batch(): FirebaseFirestoreTypes.WriteBatch;

interface WriteBatch {
  /**
   * Set document data in the batch
   * @param documentRef - Document reference to set
   * @param data - Document data to set
   * @param options - Optional set options for merging
   * @returns WriteBatch instance for chaining
   */
  set<T>(
    documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
    data: T,
    options?: FirebaseFirestoreTypes.SetOptions
  ): FirebaseFirestoreTypes.WriteBatch;

  /**
   * Update document fields in the batch
   * @param documentRef - Document reference to update
   * @param data - Partial data to update
   * @returns WriteBatch instance for chaining
   */
  update<T>(
    documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
    data: Partial<T>
  ): FirebaseFirestoreTypes.WriteBatch;

  /**
   * Update specific fields using field paths in the batch
   * @param documentRef - Document reference to update
   * @param field - Field path to update
   * @param value - New field value
   * @param moreFieldsAndValues - Additional field-value pairs
   * @returns WriteBatch instance for chaining
   */
  update<T>(
    documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
    field: keyof T | FirebaseFirestoreTypes.FieldPath,
    value: any,
    ...moreFieldsAndValues: any[]
  ): FirebaseFirestoreTypes.WriteBatch;

  /**
   * Delete a document in the batch
   * @param documentRef - Document reference to delete
   * @returns WriteBatch instance for chaining
   */
  delete(documentRef: FirebaseFirestoreTypes.DocumentReference): FirebaseFirestoreTypes.WriteBatch;

  /**
   * Commit all operations in the batch
   * @returns Promise resolving when all operations complete
   */
  commit(): Promise<void>;
}

Usage Examples:

import firestore from '@react-native-firebase/firestore';

// Bulk user creation
async function createMultipleUsers(users: any[]) {
  const db = firestore();
  const batch = db.batch();
  
  users.forEach(userData => {
    const userRef = db.collection('users').doc(); // Auto-generate ID
    batch.set(userRef, {
      ...userData,
      createdAt: firestore.FieldValue.serverTimestamp()
    });
  });
  
  await batch.commit();
  console.log(`Created ${users.length} users successfully`);
}

// Bulk update operation
async function updateUserStatuses(userIds: string[], newStatus: string) {
  const db = firestore();
  const batch = db.batch();
  
  userIds.forEach(userId => {
    const userRef = db.collection('users').doc(userId);
    batch.update(userRef, {
      status: newStatus,
      statusUpdatedAt: firestore.FieldValue.serverTimestamp()
    });
  });
  
  await batch.commit();
  console.log(`Updated ${userIds.length} user statuses`);
}

// Mixed operations in a batch
async function setupNewProject(projectData: any) {
  const db = firestore();
  const batch = db.batch();
  
  // Create project document
  const projectRef = db.collection('projects').doc();
  batch.set(projectRef, {
    ...projectData,
    createdAt: firestore.FieldValue.serverTimestamp(),
    status: 'active'
  });
  
  // Create default settings
  const settingsRef = db.collection('projects').doc(projectRef.id).collection('settings').doc('default');
  batch.set(settingsRef, {
    theme: 'light',
    notifications: true,
    language: 'en'
  });
  
  // Update user's project count
  const userRef = db.collection('users').doc(projectData.ownerId);
  batch.update(userRef, {
    projectCount: firestore.FieldValue.increment(1),
    lastProjectCreatedAt: firestore.FieldValue.serverTimestamp()
  });
  
  // Create activity log entry
  const activityRef = db.collection('activities').doc();
  batch.set(activityRef, {
    type: 'project_created',
    projectId: projectRef.id,
    userId: projectData.ownerId,
    timestamp: firestore.FieldValue.serverTimestamp()
  });
  
  await batch.commit();
  return { projectId: projectRef.id };
}

// Batch with merge operations
async function updateUserProfiles(profileUpdates: Array<{ userId: string; profileData: any }>) {
  const db = firestore();
  const batch = db.batch();
  
  profileUpdates.forEach(({ userId, profileData }) => {
    const userRef = db.collection('users').doc(userId);
    batch.set(userRef, {
      profile: profileData,
      updatedAt: firestore.FieldValue.serverTimestamp()
    }, { merge: true }); // Merge with existing data
  });
  
  await batch.commit();
}

// Conditional batch operations
async function archiveOldPosts(cutoffDate: Date) {
  const db = firestore();
  
  // First, query for old posts
  const oldPostsSnapshot = await db
    .collection('posts')
    .where('createdAt', '<', cutoffDate)
    .where('archived', '==', false)
    .limit(500) // Firestore batch limit
    .get();
  
  if (oldPostsSnapshot.empty) {
    console.log('No posts to archive');
    return;
  }
  
  const batch = db.batch();
  
  oldPostsSnapshot.docs.forEach(doc => {
    batch.update(doc.ref, {
      archived: true,
      archivedAt: firestore.FieldValue.serverTimestamp()
    });
  });
  
  await batch.commit();
  console.log(`Archived ${oldPostsSnapshot.size} posts`);
}

Best Practices

Transaction vs Batch Decision Guide:

// Use Transactions when:
// 1. You need to read data before writing
// 2. Operations depend on current document state
// 3. You need conditional logic based on existing data

// Example: Transfer money (requires reading current balances)
async function useTransaction() {
  return firestore().runTransaction(async (transaction) => {
    const accountDoc = await transaction.get(accountRef);
    const currentBalance = accountDoc.data().balance;
    
    if (currentBalance >= withdrawAmount) {
      transaction.update(accountRef, { balance: currentBalance - withdrawAmount });
    }
  });
}

// Use Batches when:
// 1. Only performing write operations
// 2. Operations are independent of existing data
// 3. Need better performance for write-only operations

// Example: Bulk create or update operations
async function useBatch() {
  const batch = firestore().batch();
  
  documents.forEach(doc => {
    const docRef = firestore().collection('items').doc();
    batch.set(docRef, doc);
  });
  
  return batch.commit();
}

Error Handling

interface FirestoreError extends Error {
  readonly code: FirebaseFirestoreTypes.FirestoreErrorCode;
  readonly message: string;
}

Error Handling Examples:

import firestore from '@react-native-firebase/firestore';

// Transaction error handling
async function safeTransfer(fromId: string, toId: string, amount: number) {
  try {
    const result = await firestore().runTransaction(async (transaction) => {
      // Transaction logic here
      return { success: true };
    });
    
    console.log('Transfer completed:', result);
  } catch (error) {
    switch (error.code) {
      case 'aborted':
        console.error('Transaction was aborted due to conflicting updates');
        break;
      case 'failed-precondition':
        console.error('Transaction failed due to precondition failure');
        break;
      case 'permission-denied':
        console.error('User does not have permission for this operation');
        break;
      default:
        console.error('Transaction failed:', error.message);
    }
    throw error;
  }
}

// Batch error handling
async function safeBatchWrite(operations: any[]) {
  try {
    const batch = firestore().batch();
    
    operations.forEach(op => {
      // Add operations to batch
    });
    
    await batch.commit();
    console.log('Batch write completed successfully');
  } catch (error) {
    console.error('Batch write failed:', error.message);
    
    if (error.code === 'invalid-argument') {
      console.error('One or more operations in the batch are invalid');
    }
    
    throw error;
  }
}

Types

interface SetOptions {
  /**
   * Whether to merge the data with existing document
   */
  merge?: boolean;
  
  /**
   * Specific fields to merge (requires merge: true)
   */
  mergeFields?: (string | FirebaseFirestoreTypes.FieldPath)[];
}

/**
 * Maximum number of operations allowed in a single batch
 */
const MAX_BATCH_SIZE = 500;

/**
 * Maximum number of documents that can be read in a single transaction
 */
const MAX_TRANSACTION_READS = 100;

Install with Tessl CLI

npx tessl i tessl/npm-react-native-firebase--firestore

docs

data-types.md

database-operations.md

index.md

modular-api.md

offline-network.md

querying-filtering.md

realtime-sync.md

transactions-batches.md

tile.json