0
# Transactions & Batches
1
2
Atomic operations for ensuring data consistency across multiple operations. Supports both read-write transactions and write-only batches.
3
4
## Capabilities
5
6
### Transactions
7
8
Execute multiple operations atomically with read and write capabilities. Transactions ensure data consistency by retrying operations if data changes during execution.
9
10
```typescript { .api }
11
/**
12
* Run a transaction with automatic retries
13
* @param updateFunction - Function that performs transaction operations
14
* @returns Promise resolving to the transaction result
15
*/
16
runTransaction<T>(updateFunction: (transaction: FirebaseFirestoreTypes.Transaction) => Promise<T>): Promise<T>;
17
18
interface Transaction {
19
/**
20
* Read a document within the transaction
21
* @param documentRef - Document reference to read
22
* @returns Promise resolving to document snapshot
23
*/
24
get<T>(documentRef: FirebaseFirestoreTypes.DocumentReference<T>): Promise<FirebaseFirestoreTypes.DocumentSnapshot<T>>;
25
26
/**
27
* Set document data within the transaction
28
* @param documentRef - Document reference to set
29
* @param data - Document data to set
30
* @param options - Optional set options for merging
31
* @returns Transaction instance for chaining
32
*/
33
set<T>(
34
documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
35
data: T,
36
options?: FirebaseFirestoreTypes.SetOptions
37
): FirebaseFirestoreTypes.Transaction;
38
39
/**
40
* Update document fields within the transaction
41
* @param documentRef - Document reference to update
42
* @param data - Partial data to update
43
* @returns Transaction instance for chaining
44
*/
45
update<T>(
46
documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
47
data: Partial<T>
48
): FirebaseFirestoreTypes.Transaction;
49
50
/**
51
* Update specific fields using field paths within the transaction
52
* @param documentRef - Document reference to update
53
* @param field - Field path to update
54
* @param value - New field value
55
* @param moreFieldsAndValues - Additional field-value pairs
56
* @returns Transaction instance for chaining
57
*/
58
update<T>(
59
documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
60
field: keyof T | FirebaseFirestoreTypes.FieldPath,
61
value: any,
62
...moreFieldsAndValues: any[]
63
): FirebaseFirestoreTypes.Transaction;
64
65
/**
66
* Delete a document within the transaction
67
* @param documentRef - Document reference to delete
68
* @returns Transaction instance for chaining
69
*/
70
delete(documentRef: FirebaseFirestoreTypes.DocumentReference): FirebaseFirestoreTypes.Transaction;
71
}
72
```
73
74
**Usage Examples:**
75
76
```typescript
77
import firestore from '@react-native-firebase/firestore';
78
79
// Transfer money between accounts atomically
80
async function transferMoney(fromAccountId: string, toAccountId: string, amount: number) {
81
const db = firestore();
82
83
return db.runTransaction(async (transaction) => {
84
// Read current balances
85
const fromAccountRef = db.collection('accounts').doc(fromAccountId);
86
const toAccountRef = db.collection('accounts').doc(toAccountId);
87
88
const fromAccountDoc = await transaction.get(fromAccountRef);
89
const toAccountDoc = await transaction.get(toAccountRef);
90
91
if (!fromAccountDoc.exists || !toAccountDoc.exists) {
92
throw new Error('One or both accounts do not exist');
93
}
94
95
const fromBalance = fromAccountDoc.data().balance;
96
const toBalance = toAccountDoc.data().balance;
97
98
if (fromBalance < amount) {
99
throw new Error('Insufficient funds');
100
}
101
102
// Perform the transfer
103
transaction.update(fromAccountRef, {
104
balance: fromBalance - amount,
105
lastTransactionAt: firestore.FieldValue.serverTimestamp()
106
});
107
108
transaction.update(toAccountRef, {
109
balance: toBalance + amount,
110
lastTransactionAt: firestore.FieldValue.serverTimestamp()
111
});
112
113
// Create transaction record
114
const transactionRef = db.collection('transactions').doc();
115
transaction.set(transactionRef, {
116
fromAccountId,
117
toAccountId,
118
amount,
119
createdAt: firestore.FieldValue.serverTimestamp(),
120
type: 'transfer'
121
});
122
123
return { transactionId: transactionRef.id, newFromBalance: fromBalance - amount };
124
});
125
}
126
127
// Increment counter with collision handling
128
async function incrementCounter(counterId: string) {
129
const db = firestore();
130
131
return db.runTransaction(async (transaction) => {
132
const counterRef = db.collection('counters').doc(counterId);
133
const counterDoc = await transaction.get(counterRef);
134
135
if (!counterDoc.exists) {
136
// Create new counter
137
transaction.set(counterRef, { count: 1 });
138
return 1;
139
} else {
140
// Increment existing counter
141
const newCount = counterDoc.data().count + 1;
142
transaction.update(counterRef, { count: newCount });
143
return newCount;
144
}
145
});
146
}
147
148
// Complex transaction with multiple operations
149
async function createOrderWithInventoryCheck(orderData: any) {
150
const db = firestore();
151
152
return db.runTransaction(async (transaction) => {
153
const results = [];
154
155
// Check inventory for all items
156
for (const item of orderData.items) {
157
const productRef = db.collection('products').doc(item.productId);
158
const productDoc = await transaction.get(productRef);
159
160
if (!productDoc.exists) {
161
throw new Error(`Product ${item.productId} not found`);
162
}
163
164
const availableStock = productDoc.data().stock;
165
if (availableStock < item.quantity) {
166
throw new Error(`Insufficient stock for product ${item.productId}`);
167
}
168
169
results.push({ productRef, currentStock: availableStock, orderQuantity: item.quantity });
170
}
171
172
// Update inventory
173
results.forEach(({ productRef, currentStock, orderQuantity }) => {
174
transaction.update(productRef, {
175
stock: currentStock - orderQuantity,
176
lastOrderAt: firestore.FieldValue.serverTimestamp()
177
});
178
});
179
180
// Create order
181
const orderRef = db.collection('orders').doc();
182
transaction.set(orderRef, {
183
...orderData,
184
status: 'confirmed',
185
createdAt: firestore.FieldValue.serverTimestamp()
186
});
187
188
return { orderId: orderRef.id };
189
});
190
}
191
```
192
193
### Write Batches
194
195
Execute multiple write operations atomically without reads. Batches are more efficient than transactions when you only need to write data.
196
197
```typescript { .api }
198
/**
199
* Create a new write batch
200
* @returns WriteBatch instance
201
*/
202
batch(): FirebaseFirestoreTypes.WriteBatch;
203
204
interface WriteBatch {
205
/**
206
* Set document data in the batch
207
* @param documentRef - Document reference to set
208
* @param data - Document data to set
209
* @param options - Optional set options for merging
210
* @returns WriteBatch instance for chaining
211
*/
212
set<T>(
213
documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
214
data: T,
215
options?: FirebaseFirestoreTypes.SetOptions
216
): FirebaseFirestoreTypes.WriteBatch;
217
218
/**
219
* Update document fields in the batch
220
* @param documentRef - Document reference to update
221
* @param data - Partial data to update
222
* @returns WriteBatch instance for chaining
223
*/
224
update<T>(
225
documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
226
data: Partial<T>
227
): FirebaseFirestoreTypes.WriteBatch;
228
229
/**
230
* Update specific fields using field paths in the batch
231
* @param documentRef - Document reference to update
232
* @param field - Field path to update
233
* @param value - New field value
234
* @param moreFieldsAndValues - Additional field-value pairs
235
* @returns WriteBatch instance for chaining
236
*/
237
update<T>(
238
documentRef: FirebaseFirestoreTypes.DocumentReference<T>,
239
field: keyof T | FirebaseFirestoreTypes.FieldPath,
240
value: any,
241
...moreFieldsAndValues: any[]
242
): FirebaseFirestoreTypes.WriteBatch;
243
244
/**
245
* Delete a document in the batch
246
* @param documentRef - Document reference to delete
247
* @returns WriteBatch instance for chaining
248
*/
249
delete(documentRef: FirebaseFirestoreTypes.DocumentReference): FirebaseFirestoreTypes.WriteBatch;
250
251
/**
252
* Commit all operations in the batch
253
* @returns Promise resolving when all operations complete
254
*/
255
commit(): Promise<void>;
256
}
257
```
258
259
**Usage Examples:**
260
261
```typescript
262
import firestore from '@react-native-firebase/firestore';
263
264
// Bulk user creation
265
async function createMultipleUsers(users: any[]) {
266
const db = firestore();
267
const batch = db.batch();
268
269
users.forEach(userData => {
270
const userRef = db.collection('users').doc(); // Auto-generate ID
271
batch.set(userRef, {
272
...userData,
273
createdAt: firestore.FieldValue.serverTimestamp()
274
});
275
});
276
277
await batch.commit();
278
console.log(`Created ${users.length} users successfully`);
279
}
280
281
// Bulk update operation
282
async function updateUserStatuses(userIds: string[], newStatus: string) {
283
const db = firestore();
284
const batch = db.batch();
285
286
userIds.forEach(userId => {
287
const userRef = db.collection('users').doc(userId);
288
batch.update(userRef, {
289
status: newStatus,
290
statusUpdatedAt: firestore.FieldValue.serverTimestamp()
291
});
292
});
293
294
await batch.commit();
295
console.log(`Updated ${userIds.length} user statuses`);
296
}
297
298
// Mixed operations in a batch
299
async function setupNewProject(projectData: any) {
300
const db = firestore();
301
const batch = db.batch();
302
303
// Create project document
304
const projectRef = db.collection('projects').doc();
305
batch.set(projectRef, {
306
...projectData,
307
createdAt: firestore.FieldValue.serverTimestamp(),
308
status: 'active'
309
});
310
311
// Create default settings
312
const settingsRef = db.collection('projects').doc(projectRef.id).collection('settings').doc('default');
313
batch.set(settingsRef, {
314
theme: 'light',
315
notifications: true,
316
language: 'en'
317
});
318
319
// Update user's project count
320
const userRef = db.collection('users').doc(projectData.ownerId);
321
batch.update(userRef, {
322
projectCount: firestore.FieldValue.increment(1),
323
lastProjectCreatedAt: firestore.FieldValue.serverTimestamp()
324
});
325
326
// Create activity log entry
327
const activityRef = db.collection('activities').doc();
328
batch.set(activityRef, {
329
type: 'project_created',
330
projectId: projectRef.id,
331
userId: projectData.ownerId,
332
timestamp: firestore.FieldValue.serverTimestamp()
333
});
334
335
await batch.commit();
336
return { projectId: projectRef.id };
337
}
338
339
// Batch with merge operations
340
async function updateUserProfiles(profileUpdates: Array<{ userId: string; profileData: any }>) {
341
const db = firestore();
342
const batch = db.batch();
343
344
profileUpdates.forEach(({ userId, profileData }) => {
345
const userRef = db.collection('users').doc(userId);
346
batch.set(userRef, {
347
profile: profileData,
348
updatedAt: firestore.FieldValue.serverTimestamp()
349
}, { merge: true }); // Merge with existing data
350
});
351
352
await batch.commit();
353
}
354
355
// Conditional batch operations
356
async function archiveOldPosts(cutoffDate: Date) {
357
const db = firestore();
358
359
// First, query for old posts
360
const oldPostsSnapshot = await db
361
.collection('posts')
362
.where('createdAt', '<', cutoffDate)
363
.where('archived', '==', false)
364
.limit(500) // Firestore batch limit
365
.get();
366
367
if (oldPostsSnapshot.empty) {
368
console.log('No posts to archive');
369
return;
370
}
371
372
const batch = db.batch();
373
374
oldPostsSnapshot.docs.forEach(doc => {
375
batch.update(doc.ref, {
376
archived: true,
377
archivedAt: firestore.FieldValue.serverTimestamp()
378
});
379
});
380
381
await batch.commit();
382
console.log(`Archived ${oldPostsSnapshot.size} posts`);
383
}
384
```
385
386
### Best Practices
387
388
**Transaction vs Batch Decision Guide:**
389
390
```typescript
391
// Use Transactions when:
392
// 1. You need to read data before writing
393
// 2. Operations depend on current document state
394
// 3. You need conditional logic based on existing data
395
396
// Example: Transfer money (requires reading current balances)
397
async function useTransaction() {
398
return firestore().runTransaction(async (transaction) => {
399
const accountDoc = await transaction.get(accountRef);
400
const currentBalance = accountDoc.data().balance;
401
402
if (currentBalance >= withdrawAmount) {
403
transaction.update(accountRef, { balance: currentBalance - withdrawAmount });
404
}
405
});
406
}
407
408
// Use Batches when:
409
// 1. Only performing write operations
410
// 2. Operations are independent of existing data
411
// 3. Need better performance for write-only operations
412
413
// Example: Bulk create or update operations
414
async function useBatch() {
415
const batch = firestore().batch();
416
417
documents.forEach(doc => {
418
const docRef = firestore().collection('items').doc();
419
batch.set(docRef, doc);
420
});
421
422
return batch.commit();
423
}
424
```
425
426
### Error Handling
427
428
```typescript { .api }
429
interface FirestoreError extends Error {
430
readonly code: FirebaseFirestoreTypes.FirestoreErrorCode;
431
readonly message: string;
432
}
433
```
434
435
**Error Handling Examples:**
436
437
```typescript
438
import firestore from '@react-native-firebase/firestore';
439
440
// Transaction error handling
441
async function safeTransfer(fromId: string, toId: string, amount: number) {
442
try {
443
const result = await firestore().runTransaction(async (transaction) => {
444
// Transaction logic here
445
return { success: true };
446
});
447
448
console.log('Transfer completed:', result);
449
} catch (error) {
450
switch (error.code) {
451
case 'aborted':
452
console.error('Transaction was aborted due to conflicting updates');
453
break;
454
case 'failed-precondition':
455
console.error('Transaction failed due to precondition failure');
456
break;
457
case 'permission-denied':
458
console.error('User does not have permission for this operation');
459
break;
460
default:
461
console.error('Transaction failed:', error.message);
462
}
463
throw error;
464
}
465
}
466
467
// Batch error handling
468
async function safeBatchWrite(operations: any[]) {
469
try {
470
const batch = firestore().batch();
471
472
operations.forEach(op => {
473
// Add operations to batch
474
});
475
476
await batch.commit();
477
console.log('Batch write completed successfully');
478
} catch (error) {
479
console.error('Batch write failed:', error.message);
480
481
if (error.code === 'invalid-argument') {
482
console.error('One or more operations in the batch are invalid');
483
}
484
485
throw error;
486
}
487
}
488
```
489
490
## Types
491
492
```typescript { .api }
493
interface SetOptions {
494
/**
495
* Whether to merge the data with existing document
496
*/
497
merge?: boolean;
498
499
/**
500
* Specific fields to merge (requires merge: true)
501
*/
502
mergeFields?: (string | FirebaseFirestoreTypes.FieldPath)[];
503
}
504
505
/**
506
* Maximum number of operations allowed in a single batch
507
*/
508
const MAX_BATCH_SIZE = 500;
509
510
/**
511
* Maximum number of documents that can be read in a single transaction
512
*/
513
const MAX_TRANSACTION_READS = 100;
514
```