Learn to Build a React Chat App in 10 Minutes

React has emerged as one of the most popular frameworks for building interactive user interfaces. Its component-based architecture, declarative approach, and rich ecosystem make it well-suited for creating real-time chat applications. In this guide, we‘ll walk through how to build a full-featured chat app in React in just 10 minutes.

Why React for Chat Apps

While there are many frameworks and libraries you could use to build a chat app, React offers several key benefits:

  1. Component-based architecture: React‘s component model allows you to break down your app into reusable, self-contained pieces. This modular approach makes it easier to reason about your code and maintain your app over time. For a chat app, you might have components for messages, user lists, input forms, etc.

  2. Declarative syntax: With React, you describe your UI as a function of state. This declarative approach means you simply express what you want your UI to look like, and React figures out how to efficiently update the DOM as state changes. This is especially useful for chat apps, where new messages need to be efficiently rendered as they stream in.

  3. Rich ecosystem: React has a large and active community, which has created a robust ecosystem of libraries, tools, and educational resources. For building chat apps, you can leverage libraries like Firebase or Socket.IO for real-time communication, Material UI or Bootstrap for pre-built UI components, and Redux or MobX for state management.

  4. High performance: React is known for its strong performance, thanks to its virtual DOM and efficient reconciliation algorithm. For chat apps that may have hundreds or thousands of messages, this performance is critical for delivering a smooth user experience.

Of course, React isn‘t the only option for building chat apps. Other popular choices include Angular, Vue, and even vanilla JavaScript. However, React‘s mix of simplicity, performance, and ecosystem make it a compelling choice.

Chat App Architecture

Before diving into code, it‘s important to understand the high-level architecture of our chat app. We‘ll be using a classic client-server model:

  • Client: Browser app built with React that sends messages and receives real-time updates
  • Server: Backend API server (Firebase in our case) that receives messages from clients and broadcasts them to other connected clients

Here‘s a simple diagram illustrating this architecture:

[Client] <-> [Server] <-> [Client]

On the client-side, our React app will have three main components:

  1. MessageList: Displays the list of chat messages
  2. MessageInput: Input field for composing new messages
  3. UserList: Displays a list of online users

We‘ll use Firebase for a serverless backend, which will handle receiving and broadcasting messages in real-time. Firebase also provides user authentication and presence functionality, so we can easily show which users are currently online.

Step 1: Project Setup

Let‘s start by creating a new React project using Create React App:

npx create-react-app react-chat-app
cd react-chat-app

Next, we‘ll install the necessary dependencies:

npm install firebase @material-ui/core react-firebase-hooks
  • firebase: Official Firebase SDK
  • @material-ui/core: Material Design UI component library
  • react-firebase-hooks: Useful hooks for working with Firebase in React

Step 2: Firebase Setup

Before we can start using Firebase in our app, we need to set up a new project and obtain our configuration keys. Head over to the Firebase Console and click "Add project".

Once your project is created, click "Database" in the left menu and create a new Cloud Firestore database. Make sure to start it in "test mode" for now.

Next, go to "Authentication" and enable the Google sign-in method under the "Sign-in method" tab.

Finally, go to "Project settings" and click "Add app" to create a new web app. Copy the config keys under "Firebase SDK snippet". It should look something like this:

const firebaseConfig = {
  apiKey: "AIzaSyCc7RbRQRGWmjzuFsbsLqQr0zIv6FhKdZg",
  authDomain: "my-chat-app-1eb56.firebaseapp.com",
  projectId: "my-chat-app-1eb56",
  // ...
};

Create a new firebase.js file in your src directory and add the following code:

import firebase from ‘firebase/app‘;
import ‘firebase/firestore‘;
import ‘firebase/auth‘;

const firebaseConfig = {
  // Your config keys here
};

firebase.initializeApp(firebaseConfig);

export const auth = firebase.auth();
export const firestore = firebase.firestore();

export const googleProvider = new firebase.auth.GoogleAuthProvider();
googleProvider.setCustomParameters({ prompt: ‘select_account‘ });

export const signInWithGoogle = () => auth.signInWithPopup(googleProvider);

This initializes the Firebase SDK with our config and exports variables we‘ll use later in our components.

Step 3: Authentication

To allow users to sign in to our chat app, we‘ll use Firebase Authentication. Firebase Auth provides an easy way to implement authentication using email/password, Google, Facebook, Twitter, and more.

For our app, we‘ll use Google sign-in. Update your App.js file with the following:

import React from ‘react‘;
import { useAuthState } from ‘react-firebase-hooks/auth‘;
import { auth, signInWithGoogle } from ‘./firebase‘;
import ChatRoom from ‘./ChatRoom‘;

function App() {
  const [user] = useAuthState(auth);

  return (
    <div className="app">
      <header>

        <SignOut />
      </header>

      <section>
        {user ? <ChatRoom /> : <SignIn />}
      </section>
    </div>
  );
}

function SignIn() {
  return (
    <button onClick={signInWithGoogle}>Sign in with Google</button>
  );
}

function SignOut() {
  return auth.currentUser && (
    <button onClick={() => auth.signOut()}>Sign Out</button>
  );
}

export default App;

The key pieces here are:

  • useAuthState hook: Gives us the currently logged in user (or null if not logged in)
  • auth and signInWithGoogle imports from our firebase.js file
  • Conditional rendering based on user state

If a user is signed in, we render the ChatRoom component. If not, we render the SignIn button.

Step 4: Displaying Messages

Now that we have authentication working, let‘s start displaying some messages! Create a new ChatRoom.js file with the following:

import React, { useState } from ‘react‘;
import { useCollectionData } from ‘react-firebase-hooks/firestore‘;
import { auth, firestore } from ‘./firebase‘;

function ChatRoom() {
  const messagesRef = firestore.collection(‘messages‘);
  const query = messagesRef.orderBy(‘createdAt‘).limit(25);

  const [messages] = useCollectionData(query, { idField: ‘id‘ });

  const [formValue, setFormValue] = useState(‘‘);

  const sendMessage = async(e) => {
    e.preventDefault();

    const { uid, photoURL } = auth.currentUser;

    await messagesRef.add({
      text: formValue,
      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
      uid,
      photoURL
    })

    setFormValue(‘‘);
  }

  return (
    <>
      <main>
        {messages && messages.map(msg => <ChatMessage key={msg.id} message={msg} />)}
      </main>

      <form onSubmit={sendMessage}>
        <input value={formValue} onChange={(e) => setFormValue(e.target.value)} placeholder="Say something" />
        <button type="submit" disabled={!formValue}>Send</button>
      </form>
    </>
  );
}

function ChatMessage(props) {
  const { text, uid, photoURL } = props.message;
  const messageClass = uid === auth.currentUser.uid ? ‘sent‘ : ‘received‘;

  return (
    <div className={`message ${messageClass}`}>
      <img src={photoURL || ‘https://i.imgur.com/t8Omqb7.png‘} />
      <p>{text}</p>
    </div>
  );
}

export default ChatRoom;

Let‘s break this down:

  • We create refs to the messages collection in Firestore
  • We create a query to order messages by createdAt timestamp and limit to 25
  • We use the useCollectionData hook to listen to the query and update messages state in real-time
  • We render a ChatMessage component for each message in the messages array
  • We create a form with a controlled input to send new messages
  • When the form is submitted, we add a new message to Firestore with the user‘s uid and photoURL

A few things to note here:

  1. By default, all users can read/write to the messages collection. In a real app, you‘d want to set up security rules to restrict access.
  2. We‘re using Firestore‘s serverTimestamp() function to generate a timestamp on the server. This avoids issues with client clocks being out of sync.
  3. We conditionally apply a sent or received CSS class to each message bubble to style them differently based on who sent the message.

Step 5: User Presence

One neat feature of Firebase is its support for user presence out of the box. With just a few lines of code, we can display which users are currently online in our chat app.

First, update your firebase.js file with the following:

export const usersRef = firebase.database().ref(‘users‘);

export const setUserStatus = (user, status) => {
  usersRef.child(user.uid).set({ status });
}

This creates a reference to a users node in the Realtime Database (separate from Cloud Firestore) and a helper function to set a user‘s status.

Next, update the App component to set the user‘s status when they sign in or out:

function App() {
  const [user] = useAuthState(auth);

  useEffect(() => {
    if (user) {
      setUserStatus(user, ‘online‘);

      return () => {
        setUserStatus(user, ‘offline‘);
      }
    }
  }, [user]);

  // ...
}

Finally, update the ChatRoom component to display a list of online users:

function ChatRoom() {
  // ...
  const [users] = useCollectionData(usersRef, { idField: ‘uid‘ });

  return (
    <>
      <aside>
        <h3>Online Users</h3>
        <ul>
          {users && users
            .filter(u => u.status === ‘online‘)
            .map(u => <OnlineUser key={u.uid} user={u} />)}
        </ul>  
      </aside>
      {/* ... */}
    </>
  );
}

function OnlineUser(props) {
  const { photoURL, displayName } = props.user;
  return (
    <li>
      <img src={photoURL || ‘https://i.imgur.com/t8Omqb7.png‘} alt="" />
      <span>{displayName}</span>
    </li>
  );
}

We listen to the users ref with useCollectionData, filter the list to only online users, and render an OnlineUser component for each one.

While this is a simplistic example, it shows how easily you can add presence to your app. You could extend this to show typing indicators, last active times, read receipts, and more.

Step 6: Deploying to Production

Now that our chat app is working, let‘s deploy it to production so others can use it! With Create React App, deployments are a breeze. Simply run:

npm run build

This will create an optimized production build in the build folder. You can then deploy this folder to any static file host like Firebase Hosting, Netlify, Vercel, etc.

For example, to deploy with Firebase Hosting:

  1. Install the Firebase CLI: npm install -g firebase-tools
  2. Login and initialize project: firebase login && firebase init
  3. Select the "Hosting" option and specify the build folder as your public directory
  4. Deploy the app: firebase deploy

Your chat app will now have a public URL that others can access and start chatting in realtime!

Going Further

Congrats on building your first full-stack React chat app! As you can see, React and Firebase make it incredibly easy to build rich, real-time experiences.

Some ideas to extend this app further:

  • User-to-user private messaging
  • Group chats and channels
  • Rich media messages (images, videos, links, etc.)
  • Typing indicators and read receipts
  • Reactions and threads
  • Push notifications
  • Bots and integrations

The sky really is the limit with modern web technologies. I hope this guide has given you the foundation and inspiration to go out and build awesome apps. Happy coding!

Similar Posts