Building a realtime chat application is one of the most practical and powerful ways to master modern mobile development. In this guide, you will build a fully functional, scalable, and production-ready chat app using:
-
React Native (0.80+)
-
Firebase
-
Cloud Firestore
-
React Navigation
We will go far beyond a basic demo. You’ll learn:
-
Proper Firebase modular setup (v10+)
-
Clean architecture separation
-
Realtime Firestore listeners
-
Authentication lifecycle management
-
Message pagination
-
Firestore indexing strategy
-
Security rules for production
-
Performance optimization
-
Scalability patterns used in real apps
By the end, you will understand not only how to build it — but how to scale it.
📱 What We Are Building
We will build:
-
Email/password authentication
-
Global chat room (v1)
-
Realtime messaging
-
Message bubble UI
-
Auto scroll behavior
-
Logout
-
Pagination ready
-
Secure Firestore rules
App flow:
Auth Flow
↓
Login / Register
↓
Chat Screen (Realtime)
↓
Send + Receive Messages
🧠 Why React Native + Firebase?
Why React Native?
-
Single codebase for iOS + Android
-
Mature ecosystem
-
Native performance
-
Huge community
Why Firebase?
-
No backend server required
-
Realtime listeners built-in
-
Authentication out of the box
-
Global infrastructure
-
Scales automatically
This combination is perfect for MVPs, startups, and production apps.
🏗 Architecture Overview
We will use a clean, modular structure:
src/
├── config/
│ └── firebase.js
├── services/
│ └── chatService.js
├── hooks/
│ ├── useAuth.js
│ └── useMessages.js
├── components/
│ └── MessageBubble.js
├── screens/
│ ├── LoginScreen.js
│ └── ChatScreen.js
└── navigation/
└── RootNavigator.js
This keeps:
-
UI is separate from logic
-
Firebase is isolated in services
-
Hooks reusable
-
Easy testing
This is how production apps are structured.
Create the React Native Project
npx @react-native-community/cli init RNFirebaseChat
cd RNFirebaseChat
Install required dependencies:
npm install firebase
npm install @react-navigation/native
npm install @react-navigation/native-stack
npm install react-native-screens react-native-safe-area-context
Install pods (iOS):
cd ios && pod install && cd ..
Run app:
npx react-native run-android
Firebase Project Setup
Go to:
👉 https://console.firebase.google.com/
Steps:
-
Create a new project
-
Add Android + iOS app
-
Enable:
-
Authentication → Email/Password
-
Firestore Database → Start in test mode (temporary)
-
Firestore Data Modeling
Version 1 (Single Room)
messages (collection)
└── messageId
├── text: string
├── uid: string
├── createdAt: timestamp
This is simple and works for MVP.
Why Not Embed Messages in One Document?
Because:
-
Firestore document size limit = 1MB
-
Writes become expensive
-
No efficient pagination
Always use one message per document.
Firebase Modular Configuration (v10+)
Create:
src/config/firebase.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
apiKey: 'YOUR_API_KEY',
authDomain: 'YOUR_AUTH_DOMAIN',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_BUCKET',
messagingSenderId: 'YOUR_ID',
appId: 'YOUR_APP_ID',
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
Authentication Hook (Clean Approach)
Create:
src/hooks/useAuth.js
import { useEffect, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../config/firebase';
export default function useAuth() {
const [user, setUser] = useState(null);
const [initializing, setInitializing] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, u => {
setUser(u);
setInitializing(false);
});
return unsubscribe;
}, []);
return { user, initializing };
}
Why use a hook?
-
Separation of concerns
-
Reusable
-
Cleaner navigation logic
Login & Register Screen
Create:
src/screens/LoginScreen.js
Key Features:
-
Email validation
-
Password validation
-
Loading state
-
Error handling
-
Clean UI
Login logic:
await signInWithEmailAndPassword(auth, email, password);
Register logic:
await createUserWithEmailAndPassword(auth, email, password);
Production Tip:
Never expose raw Firebase error messages in production. Map them to user-friendly messages.
Navigation Setup
Create:
src/navigation/RootNavigator.js
const Stack = createNativeStackNavigator();
export default function RootNavigator() {
const { user, initializing } = useAuth();
if (initializing) return null;
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{user ? (
<Stack.Screen name="Chat" component={ChatScreen} />
) : (
<Stack.Screen name="Login" component={LoginScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
);
}
This ensures automatic redirection based on the auth state.
Realtime Messages Hook
Create:
src/hooks/useMessages.js
import {
collection,
query,
orderBy,
onSnapshot
} from 'firebase/firestore';
import { db } from '../config/firebase';
export default function useMessages() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const q = query(
collection(db, 'messages'),
orderBy('createdAt', 'asc')
);
const unsubscribe = onSnapshot(q, snapshot => {
const data = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setMessages(data);
});
return unsubscribe;
}, []);
return messages;
}
How Realtime Works Internally
When you use:
onSnapshot(query, callback)
Firestore:
-
Opens WebSocket connection
-
Subscribes to the query
-
Pushes document changes
-
Syncs automatically
No polling. No refresh. Fully reactive.
Sending Messages Service
Create:
src/services/chatService.js
import { addDoc, collection, serverTimestamp } from 'firebase/firestore';
import { db, auth } from '../config/firebase';
export async function sendMessage(text) {
if (!text.trim()) return;
await addDoc(collection(db, 'messages'), {
text,
uid: auth.currentUser.uid,
createdAt: serverTimestamp(),
});
}
Separating service logic makes scaling easier.
Message UI Component
Create:
src/components/MessageBubble.js
Key logic:
const isMe = message.uid === auth.currentUser.uid;
Styles:
-
My message → Right-aligned, blue
-
Others → Left aligned, gray
-
Rounded corners
-
Padding
-
Max width 75%
Auto Scroll + FlatList Optimization
Use:
<FlatList
data={messages}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<MessageBubble message={item} />
)}
initialNumToRender={20}
maxToRenderPerBatch={20}
/>
Performance tip:
Avoid inverted unless needed — it complicates pagination.
Pagination (Scalable Version)
Instead of loading all messages:
query(
collection(db, 'messages'),
orderBy('createdAt', 'desc'),
limit(20)
)
Then load more with:
startAfter(lastVisibleDoc)
This prevents memory overload in large chats.
Firestore Security Rules (Production Safe)
Replace test mode:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /messages/{message} {
allow read, write: if request.auth != null;
}
}
}
Advanced rule example:
allow write: if request.auth != null
&& request.resource.data.uid == request.auth.uid;
This prevents spoofing user IDs.
Offline Support
Enable Firestore persistence:
import { enableIndexedDbPersistence } from 'firebase/firestore';
enableIndexedDbPersistence(db);
Benefits:
-
Offline reading
-
Automatic sync when online
-
Better UX
Indexing Strategy
When ordering by:
orderBy('createdAt')
Firestore may request a composite index.
Go to:
Firestore → Indexes → Create suggested index
Proper indexing improves:
-
Query speed
-
Billing efficiency
-
Scalability
Multi-Room Architecture (Advanced Preview)
Instead of a single collection:
rooms
└── roomId
└── messages
└── messageId
Query:
collection(db, 'rooms', roomId, 'messages')
This scales infinitely.
Error Handling & Edge Cases
Handle:
-
Network loss
-
Auth expiration
-
Duplicate send
-
Timestamp null (before serverTimestamp resolves)
-
Permission denied
Never assume a happy path.
Performance Optimization Summary
✔ Limit queries
✔ Use pagination
✔ Avoid heavy re-renders
✔ Use memoized components
✔ Use keyExtractor correctly
✔ Avoid inline functions in FlatList
Future Enhancements
You can now add:
-
Push Notifications (FCM)
-
Image uploads (Firebase Storage)
-
Message reactions
-
Seen status
-
Typing indicator
-
End-to-end encryption
-
Presence system
-
Group chat
-
Admin moderation
Final Thoughts
You now built a production-ready real-time chat app using:
-
React Native
-
Firebase
-
Cloud Firestore
More importantly, you learned:
-
Realtime architecture
-
Firestore modeling strategy
-
Security rules design
-
Scalable pagination
-
Clean separation of concerns
This is no longer a demo app.
This is the foundation of:
-
Slack-style apps
-
Marketplace chat
-
Dating apps
-
Support systems
-
Internal team chat tools
You can find the full source code on our GitHub.
We know that building beautifully designed Mobile and Web Apps from scratch can be frustrating and very time-consuming. Check Envato unlimited downloads and save development and design time.
That's just the basics. If you need more deep learning about React Native, you can take the following cheap course:
- React Native - The Practical Guide [2025]
- The Complete React Native + Hooks Course
- The Best React Native Course 2025 (From Beginner To Expert)
- React Native: Mobile App Development (CLI) [2025]
- React - The Complete Guide 2025 (incl. Next.js, Redux)
- React Native Development Simplified [2025]
- React Native by Projects: From Basics to Pro [2025]
- Full Stack Ecommerce Mobile App: React Native & Node.js 2025
- Learning Management System Mobile App using React Native
- React Native: Advanced Concepts
Thanks!
