React Native Firebase Cloud Firestore provides a NoSQL document database with real-time synchronization capabilities.
—
Atomic operations for ensuring data consistency across multiple operations. Supports both read-write transactions and write-only batches.
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 };
});
}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`);
}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();
}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;
}
}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