Build a Realtime Chat App in React Native with Firebase

by Didin J. on Feb 16, 2026 Build a Realtime Chat App in React Native with Firebase

Build a realtime chat app in React Native with Firebase Authentication and Firestore. Step-by-step 2026 guide with scalable, production-ready code.

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:

  1. Create a new project

  2. Add Android + iOS app

  3. 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.

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:

  1. Opens WebSocket connection

  2. Subscribes to the query

  3. Pushes document changes

  4. 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:

Thanks!