Hello! I recently learned using Firebase with ReactJS and thought of implementing my knowledge by creating some project. Since, I wanted to implement my firebase knowledge fast, I decided to make a very simple chat box instead of making very highly complex App which would take ages to complete.

In this post, I will share exactly how I made the live/real-time chatbox with Google OAuth. For this tutorial, we will not be using Context API or redux for holding state. We will be holding the state in the component's state.

Creating a new react App.

npx create-react-app live-chatbox

Installing necessary dependencies

npm install firebase-tools -g
npm install firebase

Setting up a Firebase project

  1. Follow this Youtube tutorial to learn how to create a new firebase project.
  2. Copy the config from the firebase console and create a new file "firebase/config.js" and paste in it. It would look somthing like this. (You can also copy your config data into .env.local file and use it here.)

// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "your apiKey",
  authDomain: "your authDomain",
  projectId: "your projectId",
  storageBucket: "your storageBucket",
  messagingSenderId: "your messagingSenderId",
  appId: "your appId",
  measurementId: "your measurementId",
};

3.Now we just have to initialize and export some variables in this file. First of all, import firebase from firebase package and then initialise firebase app using firebase.intiliazeApp(config). Also create and export two variables to initialize authentication and firestore. The final code would look like this.


// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "your apiKey",
authDomain: "your authDomain",
projectId: "your projectId",
storageBucket: "your storageBucket",
messagingSenderId: "your messagingSenderId",
appId: "your appId",
measurementId: "your measurementId",
};

// initialize firebase
const firebaseApp = firebase.initializeApp(firebaseConfig);

// initialize authentication
export const auth = firebase.auth();
//initialize firestore
export const db = firebase.firestore();

Creating the App UI

For this project, we will be having a very similar app layout which you can see below. App Structure

  1. Create a components folder (inside src folder) and create two components, Chat.js and Message.js. Also create corresponding .css files. The folder structure should now look something like this. Folder structure
  2. Let's edit App.js file and write some actual code. In this file, we will be rendering components on the basis of whether user is logged in or not. If the user if logged in, then render the Chat component, else render the Login button in the view. Also, as mentioned earlier we will be storing user information into the component state.

import { useState } from "react";
import Chat from "./components/Chat";
import "./App.css";

const App = () => {
  const [user, setUser] = useState(null);

  return user !== null ? (
    <Chat user={user} />
  ) : (
    <div className="login">
      <h1>Login</h1>
      <button>Login with Google</button>
    </div>
  );
};

export default App;

3.Now let's edit the Chat.js and Message.js file to make the UI look complete.

import { useState, useRef } from "react";
import Message from "./Message";
import "./Chat.css";

const Chat = ({ user }) => {
  const [input, setInput] = useState("");
  const [messages, setMessages] = useState([]);
  const scrollRef = useRef();

  return (
    <div className="chat__box">
      <div className="chat__header">
        <img src={user.photoURL} alt="User avatar" />
        <p>{user.displayName}</p>
      </div>
      <div className="chat__messages">
        {messages.map((message) => (
          <Message key={message.id} message={message} />
        ))}
        <div
          ref={scrollRef}
          style={{ float: "left", clear: "both", paddingTop: "4rem" }}
        ></div>
      </div>
      <div className="chat__input">
        <form>
          <input
            type="text"
            placeholder="Type a message here"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button>&rarr;</button>
        </form>
      </div>
    </div>
  );
};

export default Chat;

4.Now let's edit the Message.js to finally complete our App UI.

import "./Message.css";

const Message = ({ message }) => {
  return (
    <div className="chat__message">
      <img src={message.user.photoURL} alt="User avatar" />
      <p>{message.message}</p>
    </div>
  );
};

export default Message;

Firebase (The fun part 🔥)

  1. Now we will be coding the authentication and message sending part. In our App.js file,import firebase from firebase package and db variable from the config file create a loginWithGoogle() function and attach it as an onClick listener to the button. Also we will use useEffect hook to run a function everytime when the page renders. This function will log us back into the google account on page refresh. The code for final App.js is something like this.

import { useState, useEffect } from "react";
import Chat from "./components/Chat";
import { auth } from "./firebase/config";
import firebase from "firebase";
import "./App.css";

const App = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    auth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      }
    });
  }, []);

  const loginWithGoogle = () => {
    const provider = new firebase.auth.GoogleAuthProvider();
    auth.signInWithPopup(provider).then((result) => {
      setUser(result.user);
    });
  };

  return user !== null ? (
    <Chat user={user} />
  ) : (
    <div className="login">
      <h1>Login</h1>
      <button onClick={loginWithGoogle}>Login with Google</button>
    </div>
  );
};

export default App;

2.In our Chat component, let's add a useEffect() hook to read the firestore database on every change and take a snapshot of it so that we can retreive the new data in real-time.

import { useState, useEffect, useRef } from "react";
import { db } from "../firebase/config";
import firebase from "firebase";
import "./Chat.css";
import Message from "./Message";

const Chat = ({ user }) => {
  const [input, setInput] = useState("");
  const [messages, setMessages] = useState([]);
  const scrollRef = useRef();

  useEffect(() => {
    db.collection("messages")
      .orderBy("timestamp", "asc")
      .onSnapshot((snapshot) => {
        setMessages(
          snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }))
        );
      });
  }, []);

  return (
    <div className="chat__box">
      <div className="chat__header">
        <img src={user.photoURL} alt="User avatar" />
        <p>{user.displayName}</p>
      </div>
      <div className="chat__messages">
        {messages.map((message) => (
          <Message key={message.id} message={message} />
        ))}
        <div
          ref={scrollRef}
          style={{ float: "left", clear: "both", paddingTop: "4rem" }}
        ></div>
      </div>
      <div className="chat__input">
        <form onSubmit={sendMessages}>
          <input
            type="text"
            placeholder="Type a message here"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button>&rarr;</button>
        </form>
      </div>
    </div>
  );
};

export default Chat;

3.Now our app is ready to recieve messages, and now let's create the messaging functionality. For that, let's create a sendMessage() function that will create a message object (only if input is not empty) and then add it to the firebase firestore db. It will also scroll the view accordingly down to the bottom after every message. Our final code in Chat component will look like this.

import { useState, useEffect, useRef } from "react";
import { db } from "../firebase/config";
import firebase from "firebase";
import "./Chat.css";
import Message from "./Message";

const Chat = ({ user }) => {
  const [input, setInput] = useState("");
  const [messages, setMessages] = useState([]);
  const scrollRef = useRef();

  useEffect(() => {
    db.collection("messages")
      .orderBy("timestamp", "asc")
      .onSnapshot((snapshot) => {
        setMessages(
          snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }))
        );
      });
  }, []);

  const sendMessages = (e) => {
    e.preventDefault();

    if (input !== "") {
      const newMessage = {
        message: input,
        user: { displayName: user.displayName, photoURL: user.photoURL },
        timestamp: firebase.firestore.FieldValue.serverTimestamp(),
      };

      db.collection("messages").add(newMessage);

      setInput("");

      scrollRef.current.scrollIntoView({ behavior: "smooth" });
    }
  };

  return (
    <div className="chat__box">
      <div className="chat__header">
        <img src={user.photoURL} alt="User avatar" />
        <p>{user.displayName}</p>
      </div>
      <div className="chat__messages">
        {messages.map((message) => (
          <Message key={message.id} message={message} />
        ))}
        <div
          ref={scrollRef}
          style={{ float: "left", clear: "both", paddingTop: "4rem" }}
        ></div>
      </div>
      <div className="chat__input">
        <form onSubmit={sendMessages}>
          <input
            type="text"
            placeholder="Type a message here"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button>&rarr;</button>
        </form>
      </div>
    </div>
  );
};

export default Chat;

And now our app is finally ready to be published.

Note - All the .css files can be found in the GitHub repo mentioned below.

GitHub Repo 👇 https://github.com/shaan71845/live-chatbox

Live Demo 👇 https://live-chatbox-26e1b.web.app/