Building a Real-Time Chat App with React and Firebase
In this tutorial, we will build a real-time chat application using React and Firebase. Real-time chat apps allow users to communicate with each other instantly, with messages being delivered and displayed in real-time. React is a popular JavaScript library for building user interfaces, while Firebase is a platform that provides various backend services, including real-time database functionality. By combining React and Firebase, we can create a powerful and efficient chat app that updates in real-time.

Introduction
A real-time chat app is an application that allows users to send and receive messages in real-time. Unlike traditional messaging apps, real-time chat apps update the chat interface as soon as new messages are sent, without requiring the user to refresh the page. This provides a more seamless and interactive user experience.
React is a JavaScript library for building user interfaces. It allows developers to create reusable UI components that update efficiently and in a declarative manner. Firebase, on the other hand, is a platform that provides various backend services, including a real-time database. It offers a simple and scalable way to store and sync data in real-time.
In this tutorial, we will use React to build the chat interface and Firebase to handle the real-time updates and data storage. We will start by setting up the project and initializing Firebase. Then, we will build the chat interface, including the ability to display and send messages. We will also add user authentication to secure the chat app. Finally, we will explore additional features such as typing indicators, user presence status, and message reactions. We will conclude by deploying the app to Firebase Hosting.
Setting up the project
Before we can start building the chat app, we need to set up the project and install the necessary dependencies.
Creating a new React project
To create a new React project, we can use the create-react-app command-line tool. Open your terminal and run the following command:
npx create-react-app chat-app
This will create a new directory named chat-app and set up a basic React project inside it.
Installing Firebase
Next, we need to install the Firebase JavaScript SDK. Open your terminal and navigate to the project directory (chat-app) and run the following command:
npm install firebase
This will install the Firebase JavaScript SDK and its dependencies in our project.
Initializing Firebase in the project
To initialize Firebase in our project, we need to create a Firebase project and obtain the configuration settings.
- Go to the Firebase Console and create a new project.
- Once the project is created, click on the "Add Firebase to your web app" button. This will display the configuration settings.
- Copy the configuration settings and open the
src/firebase.jsfile in your project. - Replace the existing content of the file with the following code:
import firebase from 'firebase/app';
import 'firebase/firestore';
const firebaseConfig = {
// Paste your Firebase configuration settings here
};
firebase.initializeApp(firebaseConfig);
export default firebase;
Replace the comment // Paste your Firebase configuration settings here with the configuration settings obtained from the Firebase console.
With this setup, we have successfully installed and initialized Firebase in our React project.
Building the chat interface
Now that we have set up the project and initialized Firebase, we can start building the chat interface.
Creating the chat component
To create the chat component, we will create a new file named Chat.js in the src directory of our project. Open the file and add the following code:
import React, { useEffect, useState } from 'react';
import firebase from './firebase';
const Chat = () => {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState('');
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection('messages')
.orderBy('timestamp')
.onSnapshot(snapshot => {
const data = snapshot.docs.map(doc => doc.data());
setMessages(data);
});
return () => unsubscribe();
}, []);
const sendMessage = async () => {
if (newMessage.trim() === '') return;
await firebase.firestore().collection('messages').add({
text: newMessage,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setNewMessage('');
};
return (
<div>
<h1>Chat</h1>
<div>
{messages.map(message => (
<div key={message.id}>
<p>{message.text}</p>
</div>
))}
</div>
<input
type="text"
value={newMessage}
onChange={e => setNewMessage(e.target.value)}
/>
<button onClick={sendMessage}>Send</button>
</div>
);
};
export default Chat;
In this code, we import the necessary dependencies and initialize the state variables messages and newMessage using the useState hook. The messages state variable will store an array of messages, while the newMessage state variable will store the text of the new message being composed.
We use the useEffect hook to listen for real-time updates from the Firebase Firestore database. The onSnapshot method allows us to listen for changes in the messages collection and update the messages state variable accordingly. We also use the orderBy method to sort the messages by their timestamp field.
The sendMessage function is called when the "Send" button is clicked. It checks if the newMessage is not empty and then adds a new document to the messages collection in the Firestore database. We use the serverTimestamp method to set the timestamp field to the current server timestamp.
In the return statement, we render the chat interface. We map over the messages array and render each message as a <p> element. We also render an input field and a button for composing and sending new messages.
Displaying messages
To display the messages in the chat interface, we map over the messages array in the return statement of the Chat component and render each message as a <p> element. The key prop is set to the id of each message to ensure efficient updates.
{messages.map(message => (
<div key={message.id}>
<p>{message.text}</p>
</div>
))}
Sending messages
To send a new message, we add an input field and a button to the chat interface. The value of the input field is controlled by the newMessage state variable, and the onChange event updates the newMessage state variable as the user types.
<input
type="text"
value={newMessage}
onChange={e => setNewMessage(e.target.value)}
/>
<button onClick={sendMessage}>Send</button>
The sendMessage function is called when the "Send" button is clicked. It checks if the newMessage is not empty and then adds a new document to the messages collection in the Firestore database.
const sendMessage = async () => {
if (newMessage.trim() === '') return;
await firebase.firestore().collection('messages').add({
text: newMessage,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setNewMessage('');
};
Adding user authentication
To secure the chat app and allow users to authenticate, we can use Firebase Authentication. Follow the Firebase documentation to set up user authentication using Firebase Authentication.
Once user authentication is set up, we can update the sendMessage function to include the user's ID and display name in the message document.
const user = firebase.auth().currentUser;
await firebase.firestore().collection('messages').add({
text: newMessage,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
userId: user.uid,
displayName: user.displayName,
});
With this update, the messages collection will include the userId and displayName fields for each message, allowing us to identify the sender and display their name in the chat interface.
Real-time updates with Firebase
Firebase provides real-time updates out of the box, allowing us to listen for changes in the database and update the chat interface in real-time.
Listening for new messages
To listen for new messages in real-time, we use the onSnapshot method of the Firestore collection. In the useEffect hook, we set up a listener that updates the messages state variable whenever a new message is added to the messages collection.
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection('messages')
.orderBy('timestamp')
.onSnapshot(snapshot => {
const data = snapshot.docs.map(doc => doc.data());
setMessages(data);
});
return () => unsubscribe();
}, []);
The orderBy method is used to sort the messages by their timestamp field, ensuring that they are displayed in the correct order.
Updating the chat interface in real-time
With the listener set up, the chat interface will update in real-time as new messages are added to the Firestore database. The messages state variable is updated with the latest messages, and the interface is re-rendered to reflect the changes.
{messages.map(message => (
<div key={message.id}>
<p>{message.text}</p>
</div>
))}
As new messages are added, they will automatically appear in the chat interface without requiring the user to refresh the page.
Adding additional features
Now that we have a basic chat app with real-time updates, we can add additional features to enhance the user experience.
Typing indicators
Typing indicators can be added to show when a user is typing a message. We can use Firebase Cloud Firestore to store the typing status of each user.
To implement typing indicators:
- Add a
typingfield to the user document in the Firestore database. - Listen for changes in the
typingfield of other users and display a typing indicator when necessary.
const [typingUsers, setTypingUsers] = useState([]);
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection('users')
.doc(user.uid)
.onSnapshot(doc => {
const data = doc.data();
setTypingUsers(data.typing || []);
});
return () => unsubscribe();
}, []);
In the useEffect hook, we listen for changes in the typing field of the user document and update the typingUsers state variable accordingly.
To display the typing indicator, we can add a condition in the render method to check if the user is typing.
{typingUsers.includes(message.userId) && <p>Typing...</p>}
With this implementation, the chat interface will display a "Typing..." message whenever a user is typing a message.
User presence status
User presence status can be added to show when a user is online or offline. We can use Firebase Realtime Database to store the presence status of each user.
To implement user presence status:
- Add a
presencefield to the user document in the Firestore database. - Use Firebase Realtime Database to update the
presencefield when the user comes online or goes offline. - Listen for changes in the
presencefield of other users and display their presence status.
const [presenceStatus, setPresenceStatus] = useState(null);
useEffect(() => {
const userStatusRef = firebase.database().ref('status/' + user.uid);
const setUserOnline = async () => {
await userStatusRef.set({ online: true });
};
const setUserOffline = async () => {
await userStatusRef.onDisconnect().set({ online: false });
};
setUserOnline();
const presenceRef = firebase.database().ref('.info/connected');
presenceRef.on('value', snapshot => {
if (snapshot.val()) {
setUserOnline();
} else {
setUserOffline();
}
});
const presenceStatusRef = firebase.database().ref('status');
presenceStatusRef.on('value', snapshot => {
const data = snapshot.val();
setPresenceStatus(data);
});
return () => {
presenceRef.off();
presenceStatusRef.off();
};
}, []);
In the useEffect hook, we set up the necessary listeners to update the presence status of the user and listen for changes in the presence status of other users.
To display the presence status, we can add a condition in the render method to check if the user is online or offline.
<p>{presenceStatus && presenceStatus[message.userId]?.online ? 'Online' : 'Offline'}</p>
With this implementation, the chat interface will display the presence status of each user, updating in real-time as users come online or go offline.
Message reactions
Message reactions can be added to allow users to react to specific messages. We can use Firebase Firestore to store the reactions for each message.
To implement message reactions:
- Add a
reactionsfield to the message document in the Firestore database. - Listen for changes in the
reactionsfield of each message and update the reactions accordingly.
const [reactions, setReactions] = useState({});
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection('messages')
.onSnapshot(snapshot => {
const data = snapshot.docs.reduce((acc, doc) => {
const message = doc.data();
acc[doc.id] = message.reactions || [];
return acc;
}, {});
setReactions(data);
});
return () => unsubscribe();
}, []);
In the useEffect hook, we listen for changes in the reactions field of each message and update the reactions state variable accordingly.
To add a reaction to a message, we can add a button in the render method and update the reactions field of the message document when the button is clicked.
const addReaction = async messageId => {
const existingReactions = reactions[messageId] || [];
const newReaction = prompt('Enter reaction:');
if (newReaction === null || newReaction.trim() === '') return;
const updatedReactions = [...existingReactions, newReaction];
await firebase.firestore().collection('messages').doc(messageId).update({
reactions: updatedReactions,
});
};
{messages.map(message => (
<div key={message.id}>
<p>{message.text}</p>
<button onClick={() => addReaction(message.id)}>React</button>
<div>
{reactions[message.id]?.map((reaction, index) => (
<span key={index}>{reaction} </span>
))}
</div>
</div>
))}
With this implementation, the chat interface will display a "React" button for each message. When the button is clicked, the user will be prompted to enter a reaction, and the reaction will be added to the reactions field of the message document.
Deploying the app
To deploy the chat app, we can use Firebase Hosting. Firebase Hosting allows us to host and deploy our static assets, including our React app.
Preparing for deployment
Before deploying the app, we need to build the React app by running the following command in the terminal:
npm run build
This will create an optimized production-ready build of our app in the build directory.
Deploying to Firebase Hosting
To deploy the app to Firebase Hosting, follow these steps:
- Install the Firebase CLI by running the following command in the terminal:
npm install -g firebase-tools
- Log in to Firebase by running the following command and following the prompts:
firebase login
- Initialize Firebase in the project by running the following command and following the prompts:
firebase init
-
Select the hosting option, choose the Firebase project you created earlier, and set the
builddirectory as the public directory. -
Deploy the app by running the following command:
firebase deploy
After the deployment is complete, Firebase will provide a public URL for your app. You can share this URL with others to access the chat app.
Conclusion
In this tutorial, we have built a real-time chat app using React and Firebase. We started by setting up the project and initializing Firebase. Then, we built the chat interface, allowing users to send and receive messages in real-time. We added user authentication to secure the chat app and explored additional features such as typing indicators, user presence status, and message reactions. Finally, we deployed the app to Firebase Hosting, making it accessible to others.
By combining the power of React and Firebase, we have created a powerful and efficient chat app that updates in real-time. This app can serve as a foundation for building more complex real-time chat applications and can be customized and extended to meet specific requirements.