# Building a chat app with React and Firebase (Part 1)

In this multi-part series, I will be building a full-featured chat application with react and Firebase. The first entry in the series will focus on building a simple global chat room where all the messages will be readable by all members. Member authentication will be handled using Google Sign-in.

## Prerequisites

Before we start, ensure you have:

1. `node` and `npm` or `yarn` installed on your computer
2. Access to a Firebase account

## Bootstrapping the react project

We will use typescript for our react project. We will need the `firebase` and `react-firebase-hooks` packages to interact with the Firebase project. The `react-env` package will be useful for loading environmental variables from a `.env` file

To initialize a React project with typescript and install our dependencies open a terminal and run

```bash
npx create-react-app react-chat-app --template typescript
cd react-chat-app
npm install  --save firebase react-firebase-hooks
```

## Create Firebase App

Log in to your [Firebase Console](https://console.firebase.google.com) and click on `Add Project`.
Give your new project a name, check the confirmation checkbox and click on `Continue`.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662737898193/Dg1_i1QyC.png align="left")

Uncheck `Enable Google Analytics for this project` for a faster setup, and then click `Create project`, and then wait for the project to be created.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662738109553/u2ka5xw7F.png align="left")

Once the project is ready, click on `Continue` to go to the project home.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662738333028/SI2pIk34C.png align="left")

To allow users to login with Google Sign-in, click on `Authentication` then `Get started`.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662738466325/MxVwM79jS.png align="left")

Under the `Sign-in` method tab, select `Google`

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662738537462/DkPPGf4Gc.png align="left")

Click on the `Enable` switch and select a support email, then click on `Save`.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662738673098/mKyUOT8Jz.png align="left")

To configure a Cloud Firestore database, go back to the project home and click on `Cloud Firestore` then `Create database`

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662739016656/AwUS8OlFX.png align="left")

Select `Start in test mode`, click on `Next`, select a location and click on `Enable`.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662739223624/62ZMgfuVW.png align="left")

To include Firebase configurations in your application, go back to the project home and click on the `web` icon.
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662739675560/so2Yzfy2v.png align="left")
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662740021718/Sj4ktKsnV.png align="left")

Fill in the information by following the prompts

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662740325078/rU4bvAC1v.png align="left")

Copy the configuration details and put them in a `.env` file in the root of your project in the format

```ini
FIREBASE_API_KEY=*****************************************
FIREBASE_AUTH_DOMAIN=**********************************
FIREBASE_DATABASE_URL=**********************************
FIREBASE_PROJECT_ID=******************
FIREBASE_STORAGE_BUCKET=******************************
FIREBASE_MESSAGING_SENDER_id=*************
FIREBASE_APP_ID=******************************************
```

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1662740882324/gIx31ay_M.png align="left")

## Firebase Configuration

We will handle all our applications `Firebase` concerns in the file `src/firebase.ts`.

First we import all the requirements for working with the `Firestore` database and `Google Sign-in`.

```jsx
/* src/firebase.ts */
import {
  addDoc,
  collection,
  getFirestore,
  limit,
  orderBy,
  query,
  serverTimestamp,
} from "firebase/firestore";
import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import env from "react-dotenv";
import { initializeApp } from "firebase/app";
```

We then load the firebase configuration from the environmental variables. Next we initialize a firebase app, and get the `auth` and `firestore` variables for working with authentication and the database respectively.

```jsx
/* src/firebase.ts */

const firebaseConfig = {
  apiKey: env.FIREBASE_API_KEY,
  authDomain: env.FIREBASE_AUTH_DOMAIN,
  databaseURL: env.FIREBASE_DATABASE_URL,
  projectId: env.FIREBASE_PROJECT_ID,
  storageBucket: env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: env.FIREBASE_MESSAGING_SENDER_id,
  appId: env.FIREBASE_APP_ID,
};

const firebaseApp = initializeApp(firebaseConfig);
export const auth = getAuth(firebaseApp);
export const firestore = getFirestore(firebaseApp);
```

The next step is to define exports that handle backend functionality:

1. Signing in with google - We use a popup to allow the user to sign in with Google
2. Sending a message - We allow a logged in user to create a message in the database.
3. Retrieving the messages - We query the database for 25 messages, with the most recent first.

```ts
/* src/firebase.ts */

export const signInWithGoogle = async () => {
  const provider = new GoogleAuthProvider();
  return await signInWithPopup(auth, provider);
};

export const sendMessage = async (text: string) => {
  const { uid, photoURL } = auth.currentUser!;
  await addDoc(collection(firestore, "messages"), {
    text,
    createdAt: serverTimestamp(),
    uid,
    photoURL,
  });
};

export const publicMessagesQuery = query(
  collection(firestore, "messages"),
  orderBy("createdAt"),
  limit(25)
);
```

If the user is signed in, the application will show a `Sign Out` button, as well as the public chatroom. Otherwise, the application will show a `Sign In` button

We use the `useAuthState` hook provided by the `react-firebase-hooks` to keep track of the authentication status of the application.

The code in `App.tsx` is therefore:

```jsx
/* src/App.tsx */

import React from "react";
import "./App.css";
import { useAuthState } from "react-firebase-hooks/auth";
import { auth } from "./firebase";
import { SignIn, SignOut } from "./UserAuth";
import { ChatRoom } from "./ChatRoom";

function App() {
  const [user] = useAuthState(auth);
  return (
    <div className="App">
      <header>
        <SignOut />
      </header>
      <section>{user ? <ChatRoom /> : <SignIn />}</section>
    </div>
  );
}

export default App;
```

The code for Signing in and Signing out the user is inside `src/UserAuth.tsx`

```jsx
/* src/UserAuth.tsx */
import { auth, signInWithGoogle } from "./firebase";
import React from "react";

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

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

Inside the ChatRoom component, we need a form for sending new messages as well as the list of messages to be displayed.

```jsx
/* src/ChatRoom.tsx */

import React from "react";
import { useCollectionData } from "react-firebase-hooks/firestore";

import { publicMessagesQuery } from "./firebase";
import { MessageList } from "./MessageList";
import { ChatMessage } from "./types";
import { NewMessageForm } from "./NewMessageForm";

export function ChatRoom() {
  const [messages] = useCollectionData(publicMessagesQuery);

  return (
    <div>
      <MessageList messages={messages as ChatMessage[]} />
      <NewMessageForm />
    </div>
  );
}
```

The MessageList component receives a `messages` prop and renders each messages in a MessageItem component

```jsx
/* src/MessageList.tsx */

import React from "react";
import { MessageItem } from "./MessageItem";
import { ChatMessage } from "./types";

export function MessageList({ messages }: { messages: ChatMessage[] }) {
  return (
    <>
      <main>
        {messages &&
          messages.map((msg) => (
            <MessageItem key={`${msg.createdAt}`} message={msg} />
          ))}
      </main>
    </>
  );
}
```

The MessageItem component renders a single message with the avatar of the user who sent it

```jsx
/* src/MessageItem.tsx */

import React from "react";
import { auth } from "./firebase";
import { ChatMessage } from "./types";

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

  return (
    <div className={`message ${messageClass}`}>
      <img src={photoURL} alt="user avatar" />
      <p>{text}</p>
    </div>
  );
}
```

The NewMessageForm component allows the user to type a message and send it to the database.
Hitting the `Submit` button or `Enter` key submits the message. To create a newline, the key combination is `Shift + Enter`.

```jsx
/* src/NewMessageForm.tsx */
import { sendMessage } from "./firebase";
import React, { useState } from "react";

export function NewMessageForm() {
  const [formValue, setFormValue] = useState("");

  function addMessageEnter(event: React.KeyboardEvent) {
    if (event.key === "Enter" && event.shiftKey === false) {
      submit(formValue);
    }
  }

  function submit(message: string) {
    if (message.trim() !== "") {
      sendMessage(message);
    }
    setFormValue("");
  }

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        submit(formValue);
      }}
    >
      <div className="chat-message clearfix">
        <textarea
          required={true}
          value={formValue}
          onKeyUp={addMessageEnter}
          onChange={(e) => setFormValue(e.target.value)}
        />
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}
```

## Conclusion

In this part of the series, we created a basic chat app that allows users to send messages to a common chat room. In the next part of the series, we will extend the functionality to allow private inboxes between two users.

