Now, the world is facing a pandemic, and educational institutions like schools and colleges are moving to a completely online teaching experience. There are many services on the Internet to assist teachers, such as Google Classroom, Microsoft Team and Zoom.

Now, it seems that every day there are new services that claim to improve the teaching process. That’s why I think it’s useful to know how to make your own Google Classroom clone! This tutorial will teach you how to make a Google classroom clone.

This tutorial will teach you how to use React and Firebase so you can put all the pieces together and make a beautiful application.

What do you need?

  • A code editor. I recommend Visual Studio Code because it has an integrated terminal.
  • Install NodeJS because we are using React
  • A Google account to use Firebase
  • Know React – I don’t recommend this tutorial for beginners.

Create a React application

To create a React application, open the terminal in a secure folder and type the following command; NPM will do other work for you.

npx create-react-app app-name

Copy the code

Remember to replace app-name with the actual name you want to give your build. In my case, I named it Google-Classroom-Clone.

Once the application is installed, open Visual Studio Code in that directory. Then, open the integration terminal (Ctrl+J on Windows, Cmd+J on MacOS) and type the following command to start the application.

npm start

Copy the code

If you see the screen below, you’ve successfully created your React application.

Now, let’s do a quick cleanup of the files. You can remove the following from the project; We don’t need them.

  • logo.svg
  • setupTests.svg
  • App.test.js

Continue to open app.js and remove the logo.svg import at the top, as well as everything under the div tag in the file. Your file should now look like this.

import "./App.css";
function App() {
  return <div className="app"></div>;
}
export default App;

Copy the code

Delete the contents of app.css because we don’t need the default styles React gives us.

Next, type the following in terminal to install dependencies that will help us complete the entire project.

npm install firebase moment react-firebase-hooks recoil @material-ui/core @material-ui/icons react-router-dom

Copy the code

Firebase helped us interact easily with the Firebase service, and Moment helped us work with dates in our project. Because we are using the function-based React component, react-Firebase-hooks provide various hooks that tell us about the user’s status.

Recoil is as uncomplicated a state management library as Redux, so I decided we could use this and keep it simple.

@material- UI /core and @Material – UI/ICONS provide us with various pre-built components and SVG ICONS. Finally, react-router-dom helps us handle routing.

Set the Firebase

We will be using Firebase as the background, so please have your Google account information ready. Now, go to the Firebase console and log in. Now click Add Project and you should see the following screen.

Enter a project name. In my example, I’ll use Google-classroom-clone-article.

You will now be prompted if you want to enable Google Analytics for your project. Even though we don’t really need Google Analytics, it doesn’t hurt to keep it on. When asked to select an account, remember to select Firebase’s default account. Click Create Project.

Firebase will now allocate resources to the project. Once complete, press Continue to enter your project dashboard. Now, let’s set something up on the Firebase dashboard.

To enable authentication

In the sidebar, click on Authentication and you will see the screen below.

Click “Start”. This will enable the authentication module for you and you should see the various authentication options available.

Here we will be using Google authentication, so click On Google, press Enable, fill in the required details, and click Save.

You have successfully set up Google authentication in your Firebase project.

Enable Cloud Firestore

Firebase’s Cloud Firestore is a non-relational database, just like MongoDB. To enable Cloud Firestore, click the Firestore database in the sidebar.

Click Create database and you will be prompted with the following schema.

Remember to start the Firestore database in test mode. This is because we don’t want to worry about the production environment and safety rules in order to focus on the development side of things. However, you can change it to production mode after completing the project.

Click Next, select a database location, and then click Create. This will eventually initialize your Cloud Firestore database.

Now let’s copy our Firebase configuration. Click the gear icon on the sidebar to go to project Settings. Scroll down and you’ll see this section.

Click on the third icon ** (), which represents a web application. Give the application a name, then click Register Application **. Ignore every other step and we’ll do it manually.

Now go back to the project Settings and copy the configuration. It should look something like this.

Connect a React application to Firebase

Now that everything is set up, we can finally get to the fun part of coding. In your React app, create a new file called firebase.js and use this statement to import the Firebase package.

import firebase from "firebase";

Copy the code

Now paste the configuration. We need to initialize the application so that our React application can communicate with Firebase. To do this, use the following code.

const app = firebase.initializeApp(firebaseConfig);
const auth = app.auth();
const db = app.firestore();

Copy the code

In the code above, we are using the initializeApp() function to start a connection to Firebase. We then extract the Auth and FireStore modules from our app and store them in different variables.

Now let’s set up a few functions to help our application.

const googleProvider = new firebase.auth.GoogleAuthProvider(); // Sign in and check or create account in firestore const signInWithGoogle = async () => { try { const response = await auth.signInWithPopup(googleProvider); console.log(response.user); const user = response.user; console.log(`User ID - ${user.uid}`); const querySnapshot = await db .collection("users") .where("uid", "==", user.uid) .get(); if (querySnapshot.docs.length === 0) { // create a new user await db.collection("users").add({ uid: user.uid, enrolledClassrooms: [], }); } } catch (err) { alert(err.message); }}; const logout = () => { auth.signOut(); };Copy the code

In the code above, we are getting the GoogleAuthProvider provided by Firebase to help us with Google authentication. If any errors occur during authentication, the user is automatically transferred to the catch block and the error is displayed on the screen.

We are using Firebase’s signInWithPopup() function and passing in the Google provider to tell Firebase that we want to log in through an external provider. In this case, it’s Google.

Next, we check our Firestore database to see if the authenticated user exists in our database. If it doesn’t, we create a new entry in the database so that we think the user is registered _. _

Firebase is very clever with local storage. The authentication persists when the page reloads, and Firebase handles it under the hood. So once the user is authenticated, we don’t have to do anything.

Now, let’s create a logout() function.

Finally, export the modules so that we can use them throughout the application.

export { app, auth, db, signInWithGoogle, logout };

Copy the code

Now let’s continue configuring the router.

Configure the React the Router

We need to configure our React application to handle multiple routes for multiple screens. React-router-dom helps us do this.

Go to app.js and import the necessary components from this package.

import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

Copy the code

Now, in the empty

, add the following configuration.
<Router>
  <Switch>
    <Route exact path="/">
      Hello
    </Route>
  </Switch>
</Router>

Copy the code

You’ve probably used the React Router before, as it is used for almost all multi-page projects. But if you don’t know it, don’t worry, let’s break down the code above and see what happens here.

All of your code must be enclosed in

. This helps packages keep track of pages and components.


tells the router that this section needs to be changed as the page changes. Therefore, some components should only be displayed when the user is on a page. In our case, we’re switching screens because that’s what users usually do.

Components surrounded by

are rendered when the user is on a Route specified in path.

Now, if you notice, you can see Hello on the screen. But if you change the URL to similar [http://localhost:3000/test] (http://localhost:3000/test), you will see the Hello no longer appear. This is the power of the React Router.

Create a new component

Let’s make a new component. I strongly recommend installing the ES7 React Snippets extension in VS Code. It will help you make React components very easily.

Create a new folder called Screens and make two files called home.js and home.css. Go to home.js, start typing RFCE, and press Enter. A new React component will be created. Import the CSS file by including this declaration at the top.

import "./Home.css";

Copy the code

We will always use this approach to create components. Let’s go back to app.js and add the home page to our home page route. Don’t forget to import components like this, or you will face errors.

import Home from "./components/Home";

Copy the code

Your JSX in app.js should look like this.

<Router>
  <Switch>
    <Route exact path="/">
      <Home />
    </Route>
  </Switch>
</Router>

Copy the code

Now go to home.js and add the following layout.

<div className="home">
  <div className="home__container">
    <img
      src="https://upload.wikimedia.org/wikipedia/commons/5/59/Google_Classroom_Logo.png"
      alt="Google Classroom Image"
      className="home__image"
    />
    <button className="home__login">
      Login with Google
    </button>
  </div>
</div>

Copy the code

We won’t focus on styles in this tutorial, so use the following CSS in the home.css file.

.home {
  height: 100vh;
  width: 100vw;
  display: flex;
  align-items: center;
  justify-content: center;
}
.home__container {
  background-color: #f5f5f5;
  box-shadow: 0px 0px 2px -1px black;
  display: flex;
  flex-direction: column;
  padding: 30px;
}
.home__login {
  margin-top: 30px;
  padding: 20px;
  background-color: #2980b9;
  font-size: 18px;
  color: white;
  border: none;
  text-transform: uppercase;
  border-radius: 10px;
}

Copy the code

After saving, you should see a screen that looks like this.

Implement Google login

We’ve made a function to handle authentication in firebase.js, and now we can implement it.

Add the signInWithGoogle function to the onClick method of the “Log in with Google” button. Don’t forget to import the signInWithGoogle function. Now your JSX should look like this.

<div className="home">
  <div className="home__container">
    <img
      src="https://upload.wikimedia.org/wikipedia/commons/5/59/Google_Classroom_Logo.png"
      alt="Google Classroom Image"
      className="home__image"
    />
    <button className="home__login" onClick={signInWithGoogle}>
      Login with Google
    </button>
  </div>
</div>

Copy the code

When users log in, we use the React Router and Firebase hooks to redirect them to the dashboard. The Firebase hook always monitors for changes in the user’s authentication status, so we can use this data to check if the user is logged in. Let’s import Firebase and router hooks like this.

import { useAuthState } from "react-firebase-hooks/auth";
import { useHistory } from "react-router-dom";

Copy the code

Then, in your component, add the following lines to use the hooks.

const [user, loading, error] = useAuthState(auth);
const history = useHistory();

Copy the code

The first line gives us the state of the user. Therefore, if the user is in loading state, loading is true. If the user is not logged in, the user will be undefined or have no user data. If there is any error, it will be stored in error.

Now, History lets us route users through our code even if they don’t click on the link. We can use methods like history.push() and history.replace() to manage routes.

Finally, let’s make a useEffect() hook that redirects the user once they’ve been authenticated.

useEffect(() => {
  if (loading) return;
  if (user) history.push("/dashboard");
}, [loading, user]);

Copy the code

The code above checks if the user is logged in when their status changes. If so, it will be redirected to the/Dashboard route. I use useEffect() because I can check the user’s status during authentication status updates. So, if an already logged in user visits the home page, he will immediately be redirected to the dashboard without showing the login screen.

Now, if you try to log in with your Google account, you’ll see a blank screen because we don’t have a dashboard yet. But before we create the dashboard, we’ll create a navigation bar, which is common on most of our screens.

Create a navigation bar

Create a new folder in the SRC directory named Components, then create a new component named Navbar, and create the JS and CSS files. Put the navigation bar in our /dashboard route like this, app.js.

<Route exact path="/dashboard">
  <Navbar />
</Route>

Copy the code

Now, if you are logged in, the navigation bar component is placed. Let’s add the basic layout. First, add Firebase hooks, because we’ll need them to get user data.

const [user, loading, error] = useAuthState(auth);

Copy the code

Your document should look something like this.

import { Avatar, IconButton, MenuItem, Menu } from "@material-ui/core"; import { Add, Apps, Menu as MenuIcon } from "@material-ui/icons"; import React, { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import { auth, logout } from ".. /firebase"; import "./Navbar.css"; function Navbar() { const [user, loading, error] = useAuthState(auth); const [anchorEl, setAnchorEl] = useState(null); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <> <CreateClass /> <JoinClass /> <nav className="navbar"> <div className="navbar__left"> <IconButton> <MenuIcon  /> </IconButton> <img src="https://1000logos.net/wp-content/uploads/2021/05/Google-logo.png" alt="Google Logo" className="navbar__logo" />{" "} <span>Classroom</span> </div> <div className="navbar__right"> <IconButton aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick} > <Add /> </IconButton> <IconButton> <Apps /> </IconButton> <IconButton onClick={logout}> <Avatar src={user?.photoURL} /> </IconButton> <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose} > <MenuItem> Create Class </MenuItem> <MenuItem> Join Class </MenuItem> </Menu> </div> </nav> </> ); } export default Navbar;Copy the code

Add the following styles to navbar.css.

.navbar {
  width: 100vw;
  height: 65px;
  border-bottom: 1px solid #dcdcdc;
  display: flex;
  justify-content: space-between;
  padding: 0 20px;
  align-items: center;
}
.navbar__left {
  display: flex;
  align-items: center;
}
.navbar__left img {
  margin-right: 20px;
  margin-left: 20px;
}
.navbar__left span {
  font-size: 20px;
}
.navbar__right {
  display: flex;
  align-items: center;
}
.navbar__logo {
  height: 30px;
  width: auto;
}

Copy the code

We used the Material UI component and my own styling, so your navigation bar should look something like this.

If you click the **+** icon, you should see a menu pop up.

Now, clicking on any of the options doesn’t do anything. Let’s make a new component as a template for creating and adding a class. To do this, we need state management to determine whether the mode is on or off.

We have many components, so the Recoil state Management library is used to enhance the data for each component to access. Create a new folder called utils in SRC and a new file called Atoms. Js. The file should look something like this.

import { atom } from "recoil";
const joinDialogAtom = atom({
  key: "joinDialogAtom",
  default: false,
});
const createDialogAtom = atom({
  key: "createDialogAtom",
  default: false,
});
export { createDialogAtom, joinDialogAtom };

Copy the code

Atoms are just space to store your global data. Here, we have made two global atoms that indicate whether the “join” or “create” mode is on. By default, they are always false.

Now let’s start creating a class.

Create a class

Create a new component in the Components folder called CreateClass. We don’t need a CSS file because we’re using the Material UI component.

import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, } from "@material-ui/core"; import React, { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import { useRecoilState } from "recoil"; import { auth, db } from ".. /firebase"; import { createDialogAtom } from ".. /utils/atoms"; function CreateClass() { const [user, loading, error] = useAuthState(auth); const [open, setOpen] = useRecoilState(createDialogAtom); const [className, setClassName] = useState(""); const handleClose = () => { setOpen(false); }; return ( <div> <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" > <DialogTitle id="form-dialog-title">Create class</DialogTitle> <DialogContent> <DialogContentText> Enter the name of class and we will create a classroom for you! </DialogContentText> <TextField autoFocus margin="dense" label="Class Name" type="text" fullWidth value={className} onChange={(e) => setClassName(e.target.value)} /> </DialogContent> <DialogActions> <Button onClick={handleClose} color="primary"> Cancel </Button> <Button onClick={handleClose} color="primary"> Create </Button> </DialogActions> </Dialog> </div> ); } export default CreateClass;Copy the code

In this case, we are importing our Recoil atom and retrieving the data in it, which in our case is a Boolean value that tells us if our mode is turned on.

We will also synchronize a state with the text box so that we can retrieve data from the text box at any time.

Items on open


come from states, so if the open state is set to, the modes will appear. true

We have two buttons that turn off the mode by setting our on state to false.

Now, let’s create a function that will create a class by contacting our Cloud Firestore database.

const createClass = async () => {
  try {
    const newClass = await db.collection("classes").add({
      creatorUid: user.uid,
      name: className,
      creatorName: user.displayName,
      creatorPhoto: user.photoURL,
      posts: [],
    });
    const userRef = await db
      .collection("users")
      .where("uid", "==", user.uid)
      .get();
    const docId = userRef.docs[0].id;
    const userData = userRef.docs[0].data();
    let userClasses = userData.enrolledClassrooms;
    userClasses.push({
      id: newClass.id,
      name: className,
      creatorName: user.displayName,
      creatorPhoto: user.photoURL,
    });
    const docRef = await db.collection("users").doc(docId);
    await docRef.update({
      enrolledClassrooms: userClasses,
    });
    handleClose();
    alert("Classroom created successfully!");
  } catch (err) {
    alert(`Cannot create class - ${err.message}`);
  }
};

Copy the code

In this function, we use a try-catch block so we can handle any errors caught while contacting Firebase.

We’re creating a new entry classes in the collection with data from the textbox state and Firebase hooks, and then retrieving the user’s data from the database using the user ID.

We also add the class ids to the enrolledClasses array of our users and update the user data in our database.

Now on the create button onClick, insert this function. Your JS file should look like this.

import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, } from "@material-ui/core"; import React, { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import { useRecoilState } from "recoil"; import { auth, db } from ".. /firebase"; import { createDialogAtom } from ".. /utils/atoms"; function CreateClass() { const [user, loading, error] = useAuthState(auth); const [open, setOpen] = useRecoilState(createDialogAtom); const [className, setClassName] = useState(""); const handleClose = () => { setOpen(false); }; const createClass = async () => { try { const newClass = await db.collection("classes").add({ creatorUid: user.uid, name: className, creatorName: user.displayName, creatorPhoto: user.photoURL, posts: [], }); // add to current user's class list const userRef = await db .collection("users") .where("uid", "==", user.uid) .get(); const docId = userRef.docs[0].id; const userData = userRef.docs[0].data(); let userClasses = userData.enrolledClassrooms; userClasses.push({ id: newClass.id, name: className, creatorName: user.displayName, creatorPhoto: user.photoURL, }); const docRef = await db.collection("users").doc(docId); await docRef.update({ enrolledClassrooms: userClasses, }); handleClose(); alert("Classroom created successfully!" ); } catch (err) { alert(`Cannot create class - ${err.message}`); }}; return ( <div> <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" > <DialogTitle id="form-dialog-title">Create class</DialogTitle> <DialogContent> <DialogContentText> Enter the name of class and we will create a classroom for you! </DialogContentText> <TextField autoFocus margin="dense" label="Class Name" type="text" fullWidth value={className} onChange={(e) => setClassName(e.target.value)} /> </DialogContent> <DialogActions> <Button onClick={handleClose} color="primary"> Cancel </Button> <Button onClick={createClass} color="primary"> Create </Button> </DialogActions> </Dialog> </div> ); } export default CreateClass;Copy the code

Join a class

The basic concept of adding a class is very similar to creating one. Here’s joinclass.js, which should look like this.

import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, } from "@material-ui/core"; import React, { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import { useRecoilState } from "recoil"; import { auth, db } from ".. /firebase"; import { joinDialogAtom } from ".. /utils/atoms"; function JoinClass() { const [open, setOpen] = useRecoilState(joinDialogAtom); const [user, loading, error] = useAuthState(auth); const [classId, setClassId] = useState(""); const handleClose = () => { setOpen(false); }; const joinClass = async () => { try { // check if class exists const classRef = await db.collection("classes").doc(classId).get(); if (! classRef.exists) { return alert(`Class doesn't exist, please provide correct ID`); } const classData = await classRef.data(); // add class to user const userRef = await db.collection("users").where("uid", "==", user.uid); const userData = await (await userRef.get()).docs[0].data(); let tempClassrooms = userData.enrolledClassrooms; tempClassrooms.push({ creatorName: classData.creatorName, creatorPhoto: classData.creatorPhoto, id: classId, name: classData.name, }); await ( await userRef.get() ).docs[0].ref.update({ enrolledClassrooms: tempClassrooms, }); // alert done alert(`Enrolled in ${classData.name} successfully! `); handleClose(); } catch (err) { console.error(err); alert(err.message); }}; return ( <div className="joinClass"> <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" > <DialogTitle id="form-dialog-title">Join class</DialogTitle> <DialogContent> <DialogContentText> Enter ID of the class to join the classroom </DialogContentText> <TextField autoFocus margin="dense" label="Class Name" type="text" fullWidth value={classId} onChange={(e) => setClassId(e.target.value)} /> </DialogContent> <DialogActions> <Button onClick={handleClose} color="primary"> Cancel </Button> <Button onClick={joinClass} color="primary"> Join </Button> </DialogActions> </Dialog> </div> ); } export default JoinClass;Copy the code

The difference here is that we use another atom, which checks if the “Join class” mode is turned on and if the class exists. If it is, we add it to the enrolledClasses array of users and update users in the Firestore. It’s so simple

Now we need to connect everything in the navigation bar and set up onClick. Here’s what your navbar.js file should look like.

import { Avatar, IconButton, MenuItem, Menu } from "@material-ui/core"; import { Add, Apps, Menu as MenuIcon } from "@material-ui/icons"; import React, { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import { useRecoilState } from "recoil"; import { auth, logout } from ".. /firebase"; import { createDialogAtom, joinDialogAtom } from ".. /utils/atoms"; import CreateClass from "./CreateClass"; import JoinClass from "./JoinClass"; import "./Navbar.css"; function Navbar() { const [user, loading, error] = useAuthState(auth); const [anchorEl, setAnchorEl] = useState(null); const [createOpened, setCreateOpened] = useRecoilState(createDialogAtom); const [joinOpened, setJoinOpened] = useRecoilState(joinDialogAtom); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <> <CreateClass /> <JoinClass /> <nav className="navbar"> <div className="navbar__left"> <IconButton> <MenuIcon  /> </IconButton> <img src="https://1000logos.net/wp-content/uploads/2021/05/Google-logo.png" alt="Google Logo" className="navbar__logo" />{" "} <span>Classroom</span> </div> <div className="navbar__right"> <IconButton aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick} > <Add /> </IconButton> <IconButton> <Apps /> </IconButton> <IconButton onClick={logout}> <Avatar src={user?.photoURL} /> </IconButton> <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose} > <MenuItem onClick={() => { setCreateOpened(true); handleClose(); }} > Create Class </MenuItem> <MenuItem onClick={() => { setJoinOpened(true); handleClose(); }} > Join Class </MenuItem> </Menu> </div> </nav> </> ); } export default Navbar;Copy the code

Create dashboards

Now let’s start making the dashboard. Create a new component in the Screens folder, call it Dashboard and remember to create JS and CSS files for it. Here is the dashboard.css style design.

.dashboard__404 {
  display: flex;
  height: 100vh;
  width: 100vw;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}
.dashboard__classContainer {
  display: flex;
  padding: 30px;
  flex-wrap: wrap;
  width: 100vw;
}

Copy the code

Now, let’s make a component that displays each class. There’s nothing special about it, just styles to render the data. In Components, create a new component called ClassCard and copy the layout.

import { IconButton } from "@material-ui/core";
import { AssignmentIndOutlined, FolderOpenOutlined } from "@material-ui/icons";
import React from "react";
import { useHistory } from "react-router-dom";
import "./ClassCard.css";
function ClassCard({ name, creatorName, creatorPhoto, id, style }) {
  const history = useHistory();
  const goToClass = () => {
    history.push(`/class/${id}`);
  };
  return (
    <div className="classCard" style={style} onClick={goToClass}>
      <div className="classCard__upper">
        <div className="classCard__className">{name}</div>
        <div className="classCard__creatorName">{creatorName}</div>
        <img src={creatorPhoto} className="classCard__creatorPhoto" />
      </div>
      <div className="classCard__middle"></div>
      <div className="classCard__lower">
        <IconButton>
          <FolderOpenOutlined />
        </IconButton>
        <IconButton>
          <AssignmentIndOutlined />
        </IconButton>
      </div>
    </div>
  );
}
export default ClassCard;

Copy the code

In this case, we just take the item and render it. One thing to note is that when the user presses the card component, she is redirected to the class screen.

Here is classcard.css.

.classCard__upper {
  background-color: #008d7d;
  height: 90px;
  position: relative;
  color: white;
  padding: 10px;
  border-bottom: 1px solid #dcdcdc;
}
.classCard {
  width: 300px;
  border: 1px solid #dcdcdc;
  border-radius: 5px;
  overflow: hidden;
  cursor: pointer;
}
.classCard__middle {
  height: 190px;
  border-bottom: 1px solid #dcdcdc;
}
.classCard__creatorPhoto {
  position: absolute;
  right: 5px;
  border-radius: 9999px;
}
.classCard__className {
  font-weight: 600;
  font-size: 30px;
}
.classCard__creatorName {
  position: absolute;
  bottom: 12px;
  font-size: 15px;
}
.classCard__lower {
  display: flex;
  flex-direction: row-reverse;
}

Copy the code

Now, let’s incorporate the Dashboard component into our app.js file.

<Router>
  <Switch>
    <Route exact path="/">
      <Home />
    </Route>
    <Route exact path="/dashboard">
      <Navbar />
      <Dashboard />
    </Route>
  </Switch>
</Router>

Copy the code

Now open dashboard.js and make a function to get all of the user’s lessons.

const fetchClasses = async () => { try { await db .collection("users") .where("uid", "==", user.uid) .onSnapshot((snapshot) => { setClasses(snapshot? .docs[0]? .data()? .enrolledClassrooms); }); } catch (error) { console.error(error.message); }};Copy the code

This code is similar to when we get data from Firestore. We set up a snapshot listener so that whenever the data in the Firestore database is updated, these changes are reflected here.

Now that we have the state of the classes, we can easily render them. This is what your dashboard.js file should look like.

import React, { useEffect } from "react"; import "./Dashboard.css"; import { useAuthState } from "react-firebase-hooks/auth"; import { auth, db } from ".. /firebase"; import { useHistory } from "react-router-dom"; import { useState } from "react"; import ClassCard from ".. /components/ClassCard"; function Dashboard() { const [user, loading, error] = useAuthState(auth); const [classes, setClasses] = useState([]); const history = useHistory(); const fetchClasses = async () => { try { await db .collection("users") .where("uid", "==", user.uid) .onSnapshot((snapshot) => { setClasses(snapshot? .docs[0]? .data()? .enrolledClassrooms); }); } catch (error) { console.error(error.message); }}; useEffect(() => { if (loading) return; if (! user) history.replace("/"); }, [user, loading]); useEffect(() => { if (loading) return; fetchClasses(); }, [user, loading]); return ( <div className="dashboard"> {classes? .length === 0 ? ( <div className="dashboard__404"> No classes found! Join or create one! </div> ) : ( <div className="dashboard__classContainer"> {classes.map((individualClass) => ( <ClassCard creatorName={individualClass.creatorName} creatorPhoto={individualClass.creatorPhoto} name={individualClass.name} id={individualClass.id} style={{ marginRight: 30, marginBottom: 30 }} /> ))} </div> )} </div> ); } export default Dashboard;Copy the code

Now, if you create some classes, you should see them appear on the page.

Congratulations to you! Our dashboard is ready. Now we need to make a class screen that will display all the announcements for each class.

Creating class screens

First, let’s create a component that will help us create the class screen. We need to display a class Announcement, so we make an Announcement, and this component will receive the item and render the data.

Copy the following into your Announcement. Js file.

import { IconButton } from "@material-ui/core";
import { Menu, MoreVert } from "@material-ui/icons";
import React from "react";
import "./Announcement.css";
function Announcement({ image, name, date, content, authorId }) {
  return (
    <div className="announcement">
      <div className="announcement__informationContainer">
        <div className="announcement__infoSection">
          <div className="announcement__imageContainer">
            <img src={image} alt="Profile photo" />
          </div>
          <div className="announcement__nameAndDate">
            <div className="announcement__name">{name}</div>
            <div className="announcement__date">{date}</div>
          </div>
        </div>
        <div className="announcement__infoSection">
          <IconButton>
            <MoreVert />
          </IconButton>
        </div>
      </div>
      <div className="announcement__content">{content}</div>
    </div>
  );
}
export default Announcement;

Copy the code

There’s nothing going on here, just the basic layout. Here’s Announcement. CSS.

.announcement {
  width: 100%;
  padding: 25px;
  border-radius: 10px;
  border: 1px solid #adadad;
  margin-bottom: 20px;
}
.announcement__informationContainer {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.announcement__infoSection {
  display: flex;
  align-items: center;
}
.announcement__nameAndDate {
  margin-left: 10px;
}
.announcement__name {
  font-weight: 600;
}
.announcement__date {
  color: #424242;
  font-size: 14px;
  margin-top: 2px;
}
.announcement__imageContainer > img {
  height: 50px;
  width: 50px;
  border-radius: 9999px;
}
.announcement__content {
  margin-top: 15px;
}

Copy the code

Now, let’s create the class screen. Create a new component called Class in the Screens folder. Let’s include it in our app.js.

<Router>
  <Switch>
    <Route exact path="/">
      <Home />
    </Route>
    <Route exact path="/dashboard">
      <Navbar />
      <Dashboard />
    </Route>
    <Route exact path="/class/:id">
      <Navbar />
      <Dashboard />
    </Route>
  </Switch>
</Router>

Copy the code

One thing to note here: ID, which is the query parameter we send through the URL. We can access this ID in our class screen, thanks to the React Router. Here is the contents of class.css.

.class {
  width: 55%;
  margin: auto;
}
.class__nameBox {
  width: 100%;
  background-color: #0a9689;
  color: white;
  height: 350px;
  margin-top: 30px;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 30px;
  font-weight: bold;
  font-size: 43px;
}
.class__announce {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 20px;
  margin-bottom: 25px;
  box-shadow: 0px 1px 6px -2px black;
  justify-content: space-between;
  border-radius: 15px;
  margin-top: 20px;
}
.class__announce > img {
  height: 50px;
  width: 50px;
  border-radius: 9999px;
}
.class__announce > input {
  border: none;
  padding: 15px 20px;
  width: 100%;
  margin-left: 20px;
  margin-right: 20px;
  font-size: 17px;
  outline: none;
}

Copy the code

Now let’s focus on class.js. Again, this is the same thing we did with the component earlier.

import { IconButton } from "@material-ui/core"; import { SendOutlined } from "@material-ui/icons"; import moment from "moment"; import React from "react"; import { useEffect } from "react"; import { useState } from "react"; import { useAuthState } from "react-firebase-hooks/auth"; import { useHistory, useParams } from "react-router-dom"; import Announcement from ".. /components/Announcement"; import { auth, db } from ".. /firebase"; import "./Class.css"; function Class() { const [classData, setClassData] = useState({}); const [announcementContent, setAnnouncementContent] = useState(""); const [posts, setPosts] = useState([]); const [user, loading, error] = useAuthState(auth); const { id } = useParams(); const history = useHistory(); useEffect(() => { // reverse the array let reversedArray = classData? .posts? .reverse(); setPosts(reversedArray); }, [classData]); const createPost = async () => { try { const myClassRef = await db.collection("classes").doc(id).get(); const myClassData = await myClassRef.data(); console.log(myClassData); let tempPosts = myClassData.posts; tempPosts.push({ authorId: user.uid, content: announcementContent, date: moment().format("MMM Do YY"), image: user.photoURL, name: user.displayName, }); myClassRef.ref.update({ posts: tempPosts, }); } catch (error) { console.error(error); alert(`There was an error posting the announcement, please try again! `); }}; useEffect(() => { db.collection("classes") .doc(id) .onSnapshot((snapshot) => { const data = snapshot.data(); if (! data) history.replace("/"); console.log(data); setClassData(data); }); } []); useEffect(() => { if (loading) return; if (! user) history.replace("/"); }, [loading, user]); return ( <div className="class"> <div className="class__nameBox"> <div className="class__name">{classData? .name}</div> </div> <div className="class__announce"> <img src={user?.photoURL} alt="My image" /> <input type="text" value={announcementContent} onChange={(e) => setAnnouncementContent(e.target.value)} placeholder="Announce something to your class" /> <IconButton onClick={createPost}> <SendOutlined /> </IconButton> </div> {posts?.map((post) => ( <Announcement authorId={post.authorId} content={post.content} date={post.date} image={post.image} name={post.name} /> ))} </div> ); } export default Class;Copy the code

There’s a lot to do here, so let’s break it down.

We are setting up a snapshot listener in the useEffect() hook so that we can retrieve existing posts from the database. We then reverse the array and store it in another state. Reverse post will give us the latest post above.

We are rendering the Announcement component based on posts. Once a person creates an announcement, the array of posts is fetched from the database, a new entry is added, and the data is updated in the database.

Because we have a snapshot listener, it automatically updates the screen every time we create a post.

The class ID is in the URL bar. Other users can use this ID to join the class.

If everything is set up correctly, you should see something like this after adding a few posts.

Congratulations to you! What’s next?

You’ve successfully created a Google Classroom clone using React and Firebase! Now you can play with the code. Now you can play with the code — try new things, like editing posts, or adding comments and attachments.

I also suggest you make different clones. Exercises like this will help you understand at a deeper level how these popular apps work.

If you need this clone, check out my GitHub repository, where I’ve pushed all the code. If you want to add more functionality, you can make pull requests.

The postBuild a Google Classroom clone with React and Firebaseappeared first onLogRocket Blog.