akshay-khapare-react-native-firebase-hooks
v1.0.3
Published
Production-ready React Native Firebase hooks for efficient Firestore operations with comprehensive error handling, TypeScript support, and optimized performance
Maintainers
Readme
🔥 Akshay Khapare React Native Firebase Hooks
The most comprehensive, production-ready React Native Firebase hooks library with advanced error handling, zero crashes, and complete type safety.
✨ Key Features
- 🛡️ Zero App Crashes - Comprehensive error handling with 55+ specific error codes
- 🔄 Real-time Data - Advanced listeners with metadata and cache control
- ⚡ Performance Optimized - Efficient queries, pagination, and batch operations
- 🎯 Type Safe - Full TypeScript support with generics
- 🏗️ Production Ready - Enterprise-grade error reporting and debugging
- 🔧 Multi-Project - Seamless multi-Firebase project support
- 📊 Complete Coverage - 105 tests ensuring reliability
📦 Installation
npm install akshay-khapare-react-native-firebase-hooksRequired Peer Dependencies
npm install @react-native-firebase/app @react-native-firebase/firestore @react-native-firebase/auth @react-native-firebase/storage🚀 Quick Start
📱 React Native Components: All code examples use React Native components (
View,Text,TouchableOpacity,FlatList,TextInput, etc.). Import these from'react-native'in your components. For data persistence, examples useAsyncStoragefrom@react-native-async-storage/async-storage.
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import {
initializeFirebase,
useFirestoreSet,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
// Initialize Firebase
await initializeFirebase({
projectName: '[DEFAULT]',
config: firebaseConfig,
onError: (error) => console.error('Init failed:', error.code),
});
// Use hooks with error handling - No try/catch needed!
const { setData } = useFirestoreSet();
const createUser = async (userData) => {
const docId = await setData({
collection: 'users',
doc: 'user123',
data: userData,
onError: (error) => {
// Handle specific errors with error codes
switch (error.code) {
case ERROR_CODES.VALIDATION_DATA_EMPTY:
showToast('Please fill in user data');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Permission denied. Please login.');
break;
default:
showToast('Failed to create user');
}
},
});
if (docId) {
showToast('User created successfully!');
}
};📖 Complete API Documentation
Single Project Setup
import {
initializeFirebase,
configureFirestore,
} from 'akshay-khapare-react-native-firebase-hooks';
const setupFirebase = async () => {
await initializeFirebase({
projectName: '[DEFAULT]',
config: {
apiKey: 'your-api-key',
authDomain: 'your-project.firebaseapp.com',
projectId: 'your-project-id',
storageBucket: 'your-project.appspot.com',
messagingSenderId: '123456789',
appId: '1:123456789:web:abcdef',
},
onError: (error) => {
logError('Firebase init failed', error);
},
});
await configureFirestore({
projectName: '[DEFAULT]',
persistence: true,
cacheSizeBytes: -1, // Unlimited cache
});
};Multi-Project Setup
import { initializeMultipleFirebaseProjects } from 'akshay-khapare-react-native-firebase-hooks';
await initializeMultipleFirebaseProjects({
configs: [
{ name: 'production', config: prodConfig },
{ name: 'analytics', config: analyticsConfig },
],
onError: (error) => console.error('Multi-project setup failed:', error),
});Firebase Service Exports
The library also exports Firebase service instances for direct access:
import {
auth,
firestore,
storage,
} from 'akshay-khapare-react-native-firebase-hooks';
// Direct access to Firebase services
const AuthService = () => {
const signInUser = async () => {
try {
await auth().signInWithEmailAndPassword(email, password);
} catch (error) {
console.error('Auth error:', error);
}
};
};
const DirectFirestoreAccess = () => {
const directOperation = async () => {
// Direct Firestore access for complex operations
const batch = firestore().batch();
const userRef = firestore().collection('users').doc('user123');
batch.set(userRef, { name: 'John' });
await batch.commit();
};
};
const StorageOperations = () => {
const uploadFile = async (file) => {
const ref = storage().ref(`uploads/${file.name}`);
await ref.putFile(file);
return ref.getDownloadURL();
};
};Basic Usage
import {
useFirestoreSet,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const CreateUser = () => {
const { setData } = useFirestoreSet();
const createUser = async () => {
const docId = await setData({
collection: 'users',
doc: 'user123', // or use firestore().collection('users').doc().id for auto-ID
data: {
name: 'John Doe',
email: '[email protected]',
status: 'active',
},
merge: false, // true to merge with existing data
addTimestamp: true, // Adds updatedAt server timestamp
onError: (error) => handleSetError(error),
});
if (docId) {
console.log('User created with ID:', docId);
}
};
const handleSetError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_COLLECTION_EMPTY:
showToast('Collection name is required');
break;
case ERROR_CODES.VALIDATION_DOCUMENT_EMPTY:
showToast('Document ID is required');
break;
case ERROR_CODES.VALIDATION_DATA_INVALID:
showToast('Invalid data format');
break;
case ERROR_CODES.VALIDATION_DATA_EMPTY:
showToast('Data cannot be empty');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Permission denied');
break;
case ERROR_CODES.OPERATION_SET_FAILED:
showToast('Failed to save data');
break;
default:
showToast('An error occurred');
}
};
};Advanced Usage with Multi-Project
const { setData } = useFirestoreSet();
// Create user in specific project
await setData({
collection: 'users',
doc: userId,
data: userData,
firebaseProject: 'analytics', // Use specific project
merge: true,
addTimestamp: true,
onError: (error) => {
logToAnalytics('user_creation_failed', {
errorCode: error.code,
errorNumber: error.codeNumber,
category: error.category,
});
},
});Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------------------------------------ | -------- | ----------------------------------------- |
| collection | string | ✅ | Collection name |
| doc | string | ✅ | Document ID |
| data | Record<string, unknown> | ✅ | Document data |
| merge | boolean | ❌ | Merge with existing data (default: false) |
| addTimestamp | boolean | ❌ | Add updatedAt timestamp (default: false) |
| firebaseProject | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
Basic Document Fetching
import { useFirestoreGet, ERROR_CODES } from 'akshay-khapare-react-native-firebase-hooks';
const UserProfile = ({ userId }) => {
const { getData } = useFirestoreGet();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const fetchUser = async (source = 'default') => {
setLoading(true);
const result = await getData({
collection: 'users',
doc: userId,
source, // 'default' | 'server' | 'cache'
onError: (error) => handleGetError(error)
});
setLoading(false);
if (result) {
setUser({
id: result.id,
...result.data,
isFromCache: result.fromCache,
hasPendingWrites: result.hasPendingWrites
});
}
};
const handleGetError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_COLLECTION_EMPTY:
case ERROR_CODES.VALIDATION_DOCUMENT_EMPTY:
showToast('Invalid document reference');
break;
case ERROR_CODES.VALIDATION_SOURCE_INVALID:
showToast('Invalid data source specified');
break;
case ERROR_CODES.FIREBASE_DOCUMENT_NOT_FOUND:
showToast('User not found');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Access denied');
break;
case ERROR_CODES.OPERATION_GET_FAILED:
showToast('Failed to load user data');
break;
default:
showToast('An error occurred while loading');
}
};
return (
<View>
<TouchableOpacity onPress={() => fetchUser('cache')}>
<Text>Load from Cache</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => fetchUser('server')}>
<Text>Refresh from Server</Text>
</TouchableOpacity>
{loading && <LoadingSpinner />}
{user && <UserDisplay user={user} />}
</View>
);
};Parameters
| Parameter | Type | Required | Description |
| --------------------- | ------------------------------------ | -------- | -------------------------------- |
| collection | string | ✅ | Collection name |
| doc | string | ✅ | Document ID |
| source | 'default' \| 'server' \| 'cache' | ❌ | Data source (default: 'default') |
| firebaseProjectName | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
Response Format
interface DocumentResponse<T> {
id: string; // Document ID
exists: boolean; // Whether document exists
data: T | null; // Document data
fromCache: boolean; // Data from cache
hasPendingWrites: boolean; // Has pending writes
}Document Updates
import {
useFirestoreUpdate,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const UpdateUserProfile = () => {
const { updateData } = useFirestoreUpdate();
const updateUserStatus = async (userId, status) => {
const result = await updateData({
collection: 'users',
doc: userId,
data: {
status,
lastModified: new Date(),
},
addTimestamp: true, // Adds updatedAt server timestamp
onError: (error) => handleUpdateError(error, userId),
});
if (result) {
showToast('User status updated successfully');
}
};
const handleUpdateError = (error, userId) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_DATA_EMPTY:
showToast('No data provided for update');
break;
case ERROR_CODES.FIREBASE_DOCUMENT_NOT_FOUND:
showToast('User not found');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Permission denied to update user');
break;
case ERROR_CODES.OPERATION_UPDATE_FAILED:
showToast('Failed to update user');
logError('User update failed', { userId, error });
break;
default:
showToast('Update failed');
}
};
};Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------------------------------------ | -------- | ----------------------- |
| collection | string | ✅ | Collection name |
| doc | string | ✅ | Document ID |
| data | Partial<T> | ✅ | Update data |
| addTimestamp | boolean | ❌ | Add updatedAt timestamp |
| firebaseProject | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
Complex Queries with Pagination
import {
useFirestoreGetQuery,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const ProductsList = () => {
const { getQuery } = useFirestoreGetQuery();
const [products, setProducts] = useState([]);
const [lastDoc, setLastDoc] = useState(null);
const [loading, setLoading] = useState(false);
const loadProducts = async (loadMore = false) => {
setLoading(true);
const result = await getQuery(
{
collection: 'products',
where: [
['status', '==', 'published'],
['price', '>', 0],
['category', 'in', ['electronics', 'books']],
],
orderBy: [
['featured', 'desc'],
['createdAt', 'desc'],
],
limit: 20,
...(loadMore && lastDoc ? { startAfter: lastDoc } : {}),
onError: (error) => handleQueryError(error),
},
{
source: 'default', // Query options
}
);
setLoading(false);
if (result) {
const newProducts = result.map((doc) => ({
id: doc.id,
...doc.data,
isFromCache: doc.fromCache,
}));
setProducts((prev) =>
loadMore ? [...prev, ...newProducts] : newProducts
);
if (result.length > 0) {
setLastDoc(result[result.length - 1]);
}
}
};
const handleQueryError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_WHERE_CLAUSE_INVALID:
showToast('Invalid search criteria');
break;
case ERROR_CODES.VALIDATION_ORDER_BY_INVALID:
showToast('Invalid sorting criteria');
break;
case ERROR_CODES.VALIDATION_LIMIT_INVALID:
showToast('Invalid page size');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Access denied to products');
break;
case ERROR_CODES.OPERATION_QUERY_FAILED:
showToast('Failed to load products');
break;
default:
showToast('Search failed');
}
};
};Parameters
| Parameter | Type | Required | Description |
| --------------------- | ------------------------------------ | -------- | -------------------------------- |
| collection | string | ✅ | Collection name |
| where | [string, WhereFilterOp, unknown][] | ❌ | Where conditions |
| orderBy | [string, 'asc' \| 'desc'][] | ❌ | Sort conditions |
| limit | number | ❌ | Results limit (positive integer) |
| startAt | unknown | ❌ | Start cursor value |
| startAfter | unknown | ❌ | Start after cursor value |
| endAt | unknown | ❌ | End cursor value |
| endBefore | unknown | ❌ | End before cursor value |
| firebaseProjectName | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
Query Options
| Parameter | Type | Required | Description |
| --------- | ---------------------------------- | -------- | ----------------- |
| source | 'default' \| 'server' \| 'cache' | ❌ | Query data source |
Response Format
interface QueryResult<T> {
id: string; // Document ID
data: T; // Document data
fromCache: boolean; // Data retrieved from cache
hasPendingWrites: boolean; // Document has pending writes
}Advanced Usage with Cursors
const PaginatedQuery = () => {
const { getQuery } = useFirestoreGetQuery();
const [lastVisible, setLastVisible] = useState(null);
const [firstVisible, setFirstVisible] = useState(null);
// Forward pagination
const loadNext = async () => {
const result = await getQuery({
collection: 'posts',
orderBy: [['createdAt', 'desc']],
limit: 10,
startAfter: lastVisible, // Start after last document
onError: (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_LIMIT_INVALID:
showToast('Invalid page size specified');
break;
default:
showToast('Failed to load next page');
}
},
});
if (result && result.length > 0) {
setLastVisible(result[result.length - 1]);
if (!firstVisible) setFirstVisible(result[0]);
}
};
// Backward pagination
const loadPrevious = async () => {
const result = await getQuery({
collection: 'posts',
orderBy: [['createdAt', 'desc']],
limit: 10,
endBefore: firstVisible, // End before first document
onError: (error) => handleQueryError(error),
});
if (result && result.length > 0) {
setFirstVisible(result[0]);
setLastVisible(result[result.length - 1]);
}
};
};Real-time Collection Monitoring
import {
useCollectionListener,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const ChatMessages = ({ chatId }) => {
const { listenToCollection } = useCollectionListener();
const [messages, setMessages] = useState([]);
useEffect(() => {
const unsubscribe = listenToCollection({
collection: 'messages',
where: [['chatId', '==', chatId]],
orderBy: [['timestamp', 'desc']],
limit: 50,
includeMetadataChanges: true,
onData: (messageData) => {
const newMessages = messageData.map((doc) => ({
id: doc.id,
...doc.data,
isPending: doc.hasPendingWrites,
isFromCache: doc.fromCache,
}));
setMessages(newMessages);
},
onError: (error) => handleListenerError(error),
});
return () => unsubscribe();
}, [chatId]);
const handleListenerError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_CALLBACK_MISSING:
console.error('onData callback is required');
break;
case ERROR_CODES.LISTENER_SETUP_FAILED:
showToast('Failed to connect to chat');
break;
case ERROR_CODES.LISTENER_SNAPSHOT_ERROR:
showToast('Connection interrupted');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Access denied to chat');
break;
default:
showToast('Chat connection error');
}
};
};Parameters
| Parameter | Type | Required | Description |
| ------------------------ | ------------------------------------ | -------- | ------------------------ |
| collection | string | ✅ | Collection name |
| onData | (data: DocumentData<T>[]) => void | ✅ | Data callback |
| onError | (error: FirebaseHookError) => void | ✅ | Error handler (required) |
| where | [string, WhereFilterOp, unknown][] | ❌ | Where conditions |
| orderBy | [string, 'asc' \| 'desc'][] | ❌ | Sort conditions |
| limit | number | ❌ | Results limit |
| includeMetadataChanges | boolean | ❌ | Include metadata changes |
| firebaseProject | string | ❌ | Firebase project name |
Real-time Document Monitoring
import {
useDocumentListener,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const UserProfileLive = ({ userId }) => {
const { listenToDocument } = useDocumentListener();
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = listenToDocument({
collection: 'users',
doc: userId,
includeMetadataChanges: true,
onData: (docData) => {
if (docData.exists) {
setUser({
id: docData.id,
...docData.data,
isOnline: !docData.fromCache,
hasUnsavedChanges: docData.hasPendingWrites,
});
} else {
setUser(null);
}
},
onError: (error) => handleDocListenerError(error, userId),
});
return () => unsubscribe();
}, [userId]);
const handleDocListenerError = (error, userId) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_DOCUMENT_EMPTY:
console.error('User ID is required');
break;
case ERROR_CODES.LISTENER_SETUP_FAILED:
showToast('Failed to connect to user profile');
break;
case ERROR_CODES.FIREBASE_DOCUMENT_NOT_FOUND:
showToast('User profile not found');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Access denied to user profile');
break;
default:
showToast('Profile connection error');
}
};
};Parameters
| Parameter | Type | Required | Description |
| ------------------------ | ------------------------------------ | -------- | ------------------------ |
| collection | string | ✅ | Collection name |
| doc | string | ✅ | Document ID |
| onData | (data: DocumentData<T>) => void | ✅ | Data callback |
| onError | (error: FirebaseHookError) => void | ✅ | Error handler (required) |
| includeMetadataChanges | boolean | ❌ | Include metadata changes |
| firebaseProject | string | ❌ | Firebase project name |
Atomic Batch Operations
import {
useFirestoreTransaction,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const TransferFunds = () => {
const { executeBatch, executeTransaction } = useFirestoreTransaction();
const batchUpdateUsers = async () => {
const operations = [
{
type: 'set',
collection: 'users',
doc: 'user1',
data: { name: 'John Doe', status: 'active' },
merge: true,
addTimestamp: true,
},
{
type: 'update',
collection: 'users',
doc: 'user2',
data: { lastLogin: new Date() },
addTimestamp: true,
},
{
type: 'delete',
collection: 'users',
doc: 'user3',
},
];
const docIds = await executeBatch({
operations,
onError: (error) => handleBatchError(error),
});
if (docIds) {
showToast('Batch operation completed successfully');
}
};
const transferFunds = async (fromUserId, toUserId, amount) => {
const result = await executeTransaction({
callback: async (transaction, firestore) => {
const fromRef = firestore().collection('accounts').doc(fromUserId);
const toRef = firestore().collection('accounts').doc(toUserId);
const fromDoc = await transaction.get(fromRef);
const toDoc = await transaction.get(toRef);
if (!fromDoc.exists || !toDoc.exists) {
throw new Error('Account not found');
}
const fromBalance = fromDoc.data().balance;
const toBalance = toDoc.data().balance;
if (fromBalance < amount) {
throw new Error('Insufficient funds');
}
transaction.update(fromRef, { balance: fromBalance - amount });
transaction.update(toRef, { balance: toBalance + amount });
return {
fromBalance: fromBalance - amount,
toBalance: toBalance + amount,
};
},
onError: (error) => handleTransactionError(error),
});
if (result) {
showToast('Transfer completed successfully');
}
};
const handleBatchError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_OPERATIONS_EMPTY:
showToast('No operations provided');
break;
case ERROR_CODES.VALIDATION_OPERATIONS_LIMIT_EXCEEDED:
showToast('Too many operations (max 500)');
break;
case ERROR_CODES.VALIDATION_OPERATION_TYPE_INVALID:
showToast('Invalid operation type');
break;
case ERROR_CODES.OPERATION_BATCH_FAILED:
showToast('Batch operation failed');
break;
default:
showToast('Batch operation error');
}
};
const handleTransactionError = (error) => {
switch (error.code) {
case ERROR_CODES.OPERATION_TRANSACTION_FAILED:
showToast('Transaction failed - all changes reverted');
break;
case ERROR_CODES.FIREBASE_ABORTED:
showToast('Transaction was aborted - please retry');
break;
default:
showToast('Transaction error');
}
};
};Batch Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------------------------------------ | -------- | ----------------------------- |
| operations | BatchOperation[] | ✅ | Array of operations (max 500) |
| firebaseProject | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
BatchOperation Interface
interface BatchOperation<T = unknown> {
type: 'set' | 'update' | 'delete'; // Operation type
collection: string; // Collection name
doc: string; // Document ID
data?: T; // Data (required for set/update)
merge?: boolean; // Merge for set operations
addTimestamp?: boolean; // Add server timestamp
}Transaction Parameters
| Parameter | Type | Required | Description |
| ----------------- | ---------------------------------------- | -------- | --------------------- |
| callback | (transaction, firestore) => Promise<T> | ✅ | Transaction function |
| firebaseProject | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
Complete Batch Operations Example
const ComplexBatchOperations = () => {
const { executeBatch } = useFirestoreTransaction();
const performComplexUpdate = async () => {
const operations = [
// Set operation with merge
{
type: 'set',
collection: 'users',
doc: 'user1',
data: {
name: 'John Doe',
email: '[email protected]',
role: 'admin',
},
merge: true,
addTimestamp: true,
},
// Update operation
{
type: 'update',
collection: 'profiles',
doc: 'profile1',
data: {
lastActive: new Date(),
status: 'online',
},
addTimestamp: true,
},
// Delete operation
{
type: 'delete',
collection: 'temp_data',
doc: 'temp1',
},
];
const docIds = await executeBatch({
operations,
firebaseProject: 'production', // Optional project
onError: (error) => handleBatchError(error),
});
if (docIds) {
showToast(`Batch completed: ${docIds.length} operations`);
}
};
const handleBatchError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_OPERATIONS_EMPTY:
showToast('No operations provided');
break;
case ERROR_CODES.VALIDATION_OPERATIONS_LIMIT_EXCEEDED:
showToast('Too many operations (max 500 per batch)');
break;
case ERROR_CODES.VALIDATION_OPERATION_TYPE_INVALID:
showToast('Invalid operation type specified');
break;
case ERROR_CODES.VALIDATION_COLLECTION_EMPTY:
showToast('Collection name missing in operation');
break;
case ERROR_CODES.VALIDATION_DOCUMENT_EMPTY:
showToast('Document ID missing in operation');
break;
case ERROR_CODES.VALIDATION_DATA_INVALID:
showToast('Invalid data for set/update operation');
break;
case ERROR_CODES.VALIDATION_DATA_EMPTY:
showToast('Empty data for set/update operation');
break;
case ERROR_CODES.OPERATION_BATCH_FAILED:
showToast('Batch operation failed');
break;
default:
showToast('Batch operation error');
}
};
};Advanced Transaction Example
const AdvancedTransaction = () => {
const { executeTransaction } = useFirestoreTransaction();
const complexTransaction = async () => {
const result = await executeTransaction({
callback: async (transaction, firestore) => {
// Access multiple documents
const userRef = firestore().collection('users').doc('user123');
const accountRef = firestore().collection('accounts').doc('acc123');
const logRef = firestore().collection('logs').doc(); // Auto-generated ID
// Read operations (must be done first in transactions)
const userDoc = await transaction.get(userRef);
const accountDoc = await transaction.get(accountRef);
if (!userDoc.exists || !accountDoc.exists) {
throw new Error('Required documents not found');
}
const userData = userDoc.data();
const accountData = accountDoc.data();
// Validation logic
if (userData.status !== 'active') {
throw new Error('User account is not active');
}
if (accountData.balance < 100) {
throw new Error('Insufficient funds');
}
// Write operations
transaction.update(userRef, {
lastTransaction: new Date(),
transactionCount: (userData.transactionCount || 0) + 1,
});
transaction.update(accountRef, {
balance: accountData.balance - 100,
lastDebit: new Date(),
});
transaction.set(logRef, {
userId: 'user123',
action: 'debit',
amount: 100,
timestamp: new Date(),
balanceAfter: accountData.balance - 100,
});
return {
transactionId: logRef.id,
newBalance: accountData.balance - 100,
success: true,
};
},
firebaseProject: 'main',
onError: (error) => handleTransactionError(error),
});
if (result) {
showToast(`Transaction completed: ${result.transactionId}`);
return result;
}
};
const handleTransactionError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_CALLBACK_MISSING:
console.error('Transaction callback is required');
break;
case ERROR_CODES.OPERATION_TRANSACTION_FAILED:
showToast('Transaction failed - all changes reverted');
break;
case ERROR_CODES.FIREBASE_ABORTED:
showToast('Transaction was aborted - please retry');
break;
case ERROR_CODES.FIREBASE_FAILED_PRECONDITION:
showToast('Transaction failed due to precondition');
break;
default:
showToast('Transaction error occurred');
}
};
};Getting Document References
import React from 'react';
import {
useFirestoreRef,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const DocumentOperations = () => {
const { getFirestoreReference } = useFirestoreRef();
const getDocumentRef = (collection, docId, project) => {
try {
const docRef = getFirestoreReference(collection, docId, project);
return docRef;
} catch (error) {
handleRefError(error);
return null;
}
};
const handleRefError = (error) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_COLLECTION_EMPTY:
showToast('Collection name is required');
break;
case ERROR_CODES.VALIDATION_DOCUMENT_EMPTY:
showToast('Document ID is required');
break;
case ERROR_CODES.VALIDATION_FIELD_INVALID:
showToast('Invalid collection or document format');
break;
case ERROR_CODES.OPERATION_REFERENCE_FAILED:
showToast('Failed to create document reference');
break;
default:
showToast('Reference creation error');
}
};
// Example usage in component
const createUserReference = () => {
const userRef = getDocumentRef('users', 'user123', 'production');
if (userRef) {
console.log('Reference created:', userRef.path);
}
};
return (
<View>
<TouchableOpacity onPress={createUserReference}>
<Text>Create User Reference</Text>
</TouchableOpacity>
</View>
);
};Reference Validation Rules
The hook performs comprehensive validation on collection and document names:
Collection Name Validation
- ✅ Cannot be empty or whitespace only
- ✅ Cannot contain double forward slashes (
//) - ✅ Cannot start with forward slash (
/) - ✅ Cannot end with forward slash (
/)
Document ID Validation
- ✅ Cannot be empty or whitespace only
- ✅ Cannot contain forward slashes (
/)
Advanced Reference Operations
const AdvancedReferenceUsage = () => {
const { getFirestoreReference } = useFirestoreRef();
const validateAndCreateRef = (collection, docId) => {
try {
// The hook automatically validates format
const ref = getFirestoreReference(collection, docId);
// Use reference for operations
console.log('Reference path:', ref.path);
console.log('Collection ID:', ref.parent.id);
console.log('Document ID:', ref.id);
return ref;
} catch (error) {
// Handle validation errors
if (error.code === ERROR_CODES.VALIDATION_FIELD_INVALID) {
if (error.message.includes('forward slashes')) {
showToast('Document ID cannot contain "/" characters');
} else if (error.message.includes('invalid characters')) {
showToast('Collection name has invalid format');
}
}
return null;
}
};
// Examples of invalid references that will be caught
const testInvalidReferences = () => {
// These will all throw validation errors:
// Invalid collection names
// validateAndCreateRef('', 'doc1'); // Empty collection
// validateAndCreateRef('/users', 'doc1'); // Starts with /
// validateAndCreateRef('users/', 'doc1'); // Ends with /
// validateAndCreateRef('users//posts', 'doc1'); // Contains //
// Invalid document IDs
// validateAndCreateRef('users', ''); // Empty doc ID
// validateAndCreateRef('users', 'user/123'); // Contains /
};
return (
<View>
<TouchableOpacity onPress={() => validateAndCreateRef('users', 'user123')}>
<Text>Create Valid Reference</Text>
</TouchableOpacity>
</View>
);
};Parameters
| Parameter | Type | Required | Description |
| --------------------- | -------- | -------- | --------------------- |
| collection | string | ✅ | Collection name |
| doc | string | ✅ | Document ID |
| firebaseProjectName | string | ❌ | Firebase project name |
Document Existence Checking
import {
useIsDocumentExist,
ERROR_CODES,
} from 'akshay-khapare-react-native-firebase-hooks';
const DocumentChecker = () => {
const { isExist } = useIsDocumentExist();
const checkUserExists = async (userId) => {
const exists = await isExist({
collection: 'users',
doc: userId,
source: 'server', // Force server check
onError: (error) => handleExistsError(error, userId),
});
if (exists !== null) {
return exists ? 'User exists' : 'User not found';
}
return 'Check failed';
};
const handleExistsError = (error, userId) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_SOURCE_INVALID:
showToast('Invalid data source specified');
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Access denied');
break;
case ERROR_CODES.OPERATION_GET_FAILED:
showToast('Failed to check user existence');
logError('Existence check failed', { userId, error });
break;
default:
showToast('Existence check error');
}
};
};Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------------------------------------ | -------- | --------------------- |
| collection | string | ✅ | Collection name |
| doc | string | ✅ | Document ID |
| source | 'default' \| 'server' \| 'cache' | ❌ | Data source |
| firebaseProject | string | ❌ | Firebase project name |
| onError | (error: FirebaseHookError) => void | ❌ | Error handler |
🚨 Complete Error Handling Guide
Error Categories
import {
ERROR_CATEGORIES,
ERROR_CODES,
ERROR_CODE_NUMBERS,
} from 'akshay-khapare-react-native-firebase-hooks';
// Available categories
ERROR_CATEGORIES.VALIDATION; // Input validation errors
ERROR_CATEGORIES.FIREBASE; // Firebase-specific errors
ERROR_CATEGORIES.NETWORK; // Network connectivity errors
ERROR_CATEGORIES.INITIALIZATION; // Setup and config errors
ERROR_CATEGORIES.OPERATION; // CRUD operation errors
ERROR_CATEGORIES.LISTENER; // Real-time listener errorsValidation Errors (1000-1999)
| Code | Number | Description |
| -------------------------------------- | ------ | -------------------------- |
| VALIDATION_COLLECTION_EMPTY | 1001 | Collection name is empty |
| VALIDATION_DOCUMENT_EMPTY | 1002 | Document ID is empty |
| VALIDATION_DATA_INVALID | 1003 | Invalid data format |
| VALIDATION_DATA_EMPTY | 1004 | Data object is empty |
| VALIDATION_CALLBACK_MISSING | 1005 | Required callback missing |
| VALIDATION_CALLBACK_INVALID | 1006 | Invalid callback function |
| VALIDATION_FIELD_MISSING | 1007 | Required field missing |
| VALIDATION_FIELD_INVALID | 1008 | Invalid field format |
| VALIDATION_LIMIT_INVALID | 1009 | Invalid limit value |
| VALIDATION_OPERATIONS_EMPTY | 1010 | Operations array is empty |
| VALIDATION_OPERATIONS_LIMIT_EXCEEDED | 1011 | Too many operations (>500) |
| VALIDATION_OPERATION_TYPE_INVALID | 1012 | Invalid operation type |
| VALIDATION_WHERE_CLAUSE_INVALID | 1013 | Invalid where condition |
| VALIDATION_ORDER_BY_INVALID | 1014 | Invalid orderBy condition |
| VALIDATION_PROJECT_NAME_EMPTY | 1015 | Project name is empty |
| VALIDATION_CONFIG_MISSING | 1016 | Configuration missing |
| VALIDATION_CONFIG_FIELD_MISSING | 1017 | Config field missing |
| VALIDATION_SOURCE_INVALID | 1018 | Invalid data source |
Firebase Errors (2000-2999)
| Code | Number | Description |
| ------------------------------- | ------ | ---------------------------- |
| FIREBASE_APP_NOT_INITIALIZED | 2001 | Firebase app not initialized |
| FIREBASE_PROJECT_NOT_FOUND | 2002 | Firebase project not found |
| FIREBASE_DOCUMENT_NOT_FOUND | 2003 | Document not found |
| FIREBASE_COLLECTION_NOT_FOUND | 2004 | Collection not found |
| FIREBASE_PERMISSION_DENIED | 2005 | Permission denied |
| FIREBASE_QUOTA_EXCEEDED | 2006 | Quota exceeded |
| FIREBASE_INVALID_ARGUMENT | 2007 | Invalid argument |
| FIREBASE_FAILED_PRECONDITION | 2008 | Failed precondition |
| FIREBASE_ABORTED | 2009 | Operation aborted |
| FIREBASE_OUT_OF_RANGE | 2010 | Out of range |
| FIREBASE_UNIMPLEMENTED | 2011 | Unimplemented |
| FIREBASE_INTERNAL | 2012 | Internal error |
| FIREBASE_UNAVAILABLE | 2013 | Service unavailable |
| FIREBASE_DATA_LOSS | 2014 | Data loss |
| FIREBASE_UNAUTHENTICATED | 2015 | User not authenticated |
Network Errors (3000-3999)
| Code | Number | Description |
| --------------------------- | ------ | ------------------- |
| NETWORK_UNAVAILABLE | 3001 | Network unavailable |
| NETWORK_TIMEOUT | 3002 | Network timeout |
| NETWORK_CONNECTION_FAILED | 3003 | Connection failed |
| NETWORK_DNS_FAILURE | 3004 | DNS failure |
Initialization Errors (4000-4999)
| Code | Number | Description |
| ------------------------- | ------ | ------------------------------- |
| INIT_FIREBASE_FAILED | 4001 | Firebase initialization failed |
| INIT_FIRESTORE_FAILED | 4002 | Firestore initialization failed |
| INIT_CONFIG_INVALID | 4003 | Invalid configuration |
| INIT_PROJECT_EXISTS | 4004 | Project already exists |
| INIT_CACHE_SIZE_INVALID | 4005 | Invalid cache size |
Operation Errors (5000-5999)
| Code | Number | Description |
| ------------------------------ | ------ | ------------------------- |
| OPERATION_SET_FAILED | 5001 | Set operation failed |
| OPERATION_GET_FAILED | 5002 | Get operation failed |
| OPERATION_UPDATE_FAILED | 5003 | Update operation failed |
| OPERATION_DELETE_FAILED | 5004 | Delete operation failed |
| OPERATION_QUERY_FAILED | 5005 | Query operation failed |
| OPERATION_BATCH_FAILED | 5006 | Batch operation failed |
| OPERATION_TRANSACTION_FAILED | 5007 | Transaction failed |
| OPERATION_REFERENCE_FAILED | 5008 | Reference creation failed |
Listener Errors (6000-6999)
| Code | Number | Description |
| --------------------------------- | ------ | ---------------------- |
| LISTENER_SETUP_FAILED | 6001 | Listener setup failed |
| LISTENER_DATA_PROCESSING_FAILED | 6002 | Data processing failed |
| LISTENER_UNSUBSCRIBE_FAILED | 6003 | Unsubscribe failed |
| LISTENER_SNAPSHOT_ERROR | 6004 | Snapshot error |
Unknown Errors (9000-9999)
| Code | Number | Description |
| --------------- | ------ | ---------------------- |
| UNKNOWN_ERROR | 9001 | Unknown error occurred |
Global Error Handler
import {
ERROR_CODES,
ERROR_CATEGORIES,
} from 'akshay-khapare-react-native-firebase-hooks';
const globalErrorHandler = (error) => {
// Log to analytics
logToAnalytics('firebase_error', {
code: error.code,
codeNumber: error.codeNumber,
category: error.category,
message: error.message,
timestamp: error.timestamp,
details: error.details,
});
// Handle by category
switch (error.category) {
case ERROR_CATEGORIES.VALIDATION:
showToast('Please check your input and try again');
break;
case ERROR_CATEGORIES.FIREBASE:
handleFirebaseError(error);
break;
case ERROR_CATEGORIES.NETWORK:
showToast('Network error. Please check your connection.');
break;
case ERROR_CATEGORIES.OPERATION:
showToast('Operation failed. Please try again.');
break;
default:
showToast('An unexpected error occurred');
}
};
const handleFirebaseError = (error) => {
switch (error.code) {
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
redirectToLogin();
break;
case ERROR_CODES.FIREBASE_QUOTA_EXCEEDED:
showMaintenanceMessage();
break;
case ERROR_CODES.FIREBASE_UNAUTHENTICATED:
refreshAuth();
break;
default:
showToast('Firebase service error');
}
};Error Retry Logic
const retryableErrorCodes = [
ERROR_CODES.NETWORK_UNAVAILABLE,
ERROR_CODES.FIREBASE_UNAVAILABLE,
ERROR_CODES.FIREBASE_ABORTED,
];
const withRetry = async (operation, maxRetries = 3) => {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await operation({
onError: (error) => {
lastError = error;
if (!retryableErrorCodes.includes(error.code)) {
throw error; // Don't retry non-retryable errors
}
},
});
if (result !== null) return result; // Success
if (
attempt < maxRetries &&
retryableErrorCodes.includes(lastError?.code)
) {
await delay(Math.pow(2, attempt) * 1000); // Exponential backoff
continue;
}
throw lastError;
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
};
// Usage
const data = await withRetry(() =>
getData({
collection: 'users',
doc: 'user123',
})
);Production Error Reporting
const productionErrorHandler = (error) => {
// Send to crash reporting service
crashAnalytics.recordError(error, {
errorCode: error.code,
errorNumber: error.codeNumber,
category: error.category,
context: error.details,
});
// Send to custom logging service
logService.error('Firebase Hook Error', {
code: error.code,
message: error.message,
timestamp: error.timestamp,
userID: getCurrentUserId(),
sessionID: getSessionId(),
appVersion: getAppVersion(),
details: error.details,
});
// Update error metrics
errorMetrics.increment(`firebase_error.${error.category}.${error.code}`);
};🔧 Advanced Usage Examples
Smart Data Loading Component
import { useFirestoreGet, useCollectionListener, ERROR_CODES } from 'akshay-khapare-react-native-firebase-hooks';
const SmartDataLoader = ({ collection, doc, realTime = false }) => {
const { getData } = useFirestoreGet();
const { listenToCollection } = useCollectionListener();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const handleError = useCallback((error) => {
setError(error);
setLoading(false);
// Production error handling
switch (error.code) {
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
trackEvent('access_denied', { collection, doc });
break;
case ERROR_CODES.FIREBASE_QUOTA_EXCEEDED:
trackEvent('quota_exceeded', { collection });
notifyDevTeam('Quota exceeded', error.details);
break;
default:
trackEvent('data_load_error', {
collection,
doc,
errorCode: error.code
});
}
}, [collection, doc]);
useEffect(() => {
if (realTime && !doc) {
// Real-time collection
const unsubscribe = listenToCollection({
collection,
onData: (data) => {
setData(data);
setLoading(false);
setError(null);
},
onError: handleError
});
return unsubscribe;
} else if (doc) {
// Single document fetch
const fetchData = async () => {
setLoading(true);
const result = await getData({
collection,
doc,
onError: handleError
});
if (result) {
setData(result);
setLoading(false);
setError(null);
}
};
fetchData();
}
}, [collection, doc, realTime, getData, listenToCollection, handleError]);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorDisplay error={error} onRetry={() => {
setLoading(true);
setError(null);
// Trigger re-fetch by updating a state or calling fetch function again
}} />;
return <DataDisplay data={data} />;
};Form with Auto-Save
const AutoSaveForm = ({ collection, doc, initialData }) => {
const { setData } = useFirestoreSet();
const { updateData } = useFirestoreUpdate();
const [formData, setFormData] = useState(initialData);
const [saveStatus, setSaveStatus] = useState('saved');
const autoSave = useCallback(
debounce(async (data) => {
setSaveStatus('saving');
const operation = doc ? updateData : setData;
const result = await operation({
collection,
doc: doc || generateId(),
data,
addTimestamp: true,
onError: (error) => {
setSaveStatus('error');
handleFormError(error, data);
}
});
if (result) {
setSaveStatus('saved');
trackEvent('auto_save_success', { collection, doc });
}
}, 2000),
[collection, doc, setData, updateData]
);
const handleFormError = (error, data) => {
switch (error.code) {
case ERROR_CODES.VALIDATION_DATA_EMPTY:
// Don't show error for empty auto-save
break;
case ERROR_CODES.FIREBASE_PERMISSION_DENIED:
showToast('Permission denied. Changes not saved.');
break;
case ERROR_CODES.FIREBASE_QUOTA_EXCEEDED:
showToast('Storage limit reached. Please contact support.');
break;
default:
showToast('Auto-save failed. Your changes may be lost.');
// Backup to AsyncStorage (React Native) or SecureStore
AsyncStorage.setItem(`backup_${collection}_${doc}`, JSON.stringify(data));
}
};
useEffect(() => {
if (formData !== initialData) {
autoSave(formData);
}
}, [formData, initialData, autoSave]);
return (
<View>
<SaveStatus status={saveStatus} />
{/* Form fields using TextInput, TouchableOpacity, etc. */}
</View>
);
};📊 Performance & Best Practices
Hook Optimization Tips
- Use
source: 'cache'for better performance when appropriate - Implement proper cleanup for listeners to prevent memory leaks
- Use
includeMetadataChanges: falseunless you need metadata updates - Implement pagination for large datasets using cursor-based queries
- Use batch operations for multiple writes to improve performance
Error Handling Best Practices
- Always provide
onErrorcallbacks in production - Implement retry logic for transient errors
- Use error codes for specific user messaging
- Log errors with context for debugging
- Implement fallback UI states for error scenarios
Type Safety
- Always use TypeScript interfaces for your data models
- Leverage generic types for better type inference
- Use strict type checking in your TypeScript config
🤝 Contributing
We welcome contributions! Please read our Contributing Guide for details.
📄 License
MIT © Akshay Khapare
🆘 Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📖 Documentation: Full Docs
Made with ❤️ for the React Native community
