Agora que o mundo inteiro está enfrentando uma pandemia, instituições educacionais como escolas e faculdades estão mudando para uma experiência de ensino totalmente online. Existem muitos serviços na Internet que auxiliam os professores com isso, como Google Classroom, Microsoft Teams e Zoom.
Agora, parece que novos serviços estão surgindo a cada dia que pretendem melhorar o processo de ensino. É por isso que achei que seria útil saber como fazer seu próprio clone do Google Classroom!
Este tutorial irá ensiná-lo a trabalhar com React e Firebase para que você possa juntar todas as peças para fazer um aplicativo Banger.
O que você vai precisar?
Um editor de código. Eu recomendo Visual Studio Code , porque tem um terminal integrado NodeJS instalado, porque estamos usando React A conta do Google para usar o Firebase Conhecimento prático do React-eu não recomendaria este tutorial para iniciantes
Criando um aplicativo React
Agora, vamos começar a trabalhar nesta construção divertida! Para criar um aplicativo React, abra um terminal em uma pasta segura e digite o seguinte comando; O npm fará o resto do trabalho para você:
npx create-react-app app-name
Lembre-se de substituir o app-name pelo nome real que deseja dar à compilação. No meu caso, chamei-o de google-classroom-clone.
Depois de instalar o aplicativo, abra o código do Visual Studio nesse diretório. Em seguida, abra o Terminal Integrado (Ctrl + J no Windows e Cmd + J no MacOS) e digite o seguinte comando para iniciar o aplicativo:
npm start
Se você vir a tela a seguir, foi bem-sucedido criou seu aplicativo React:
Agora, vamos fazer uma limpeza rápida dos arquivos. Você pode excluir o seguinte do projeto; não precisamos deles:
logo.svg setupTests.svg App.test.js
Vá em frente e abra o App.js para remover a importação logo.svg na parte superior, junto com tudo sob a tag div no arquivo. Seu arquivo deve ter a seguinte aparência:
import”./App.css”; function App () {return
; } exportar aplicativo padrão;
Remova o conteúdo de App.css porque não precisamos do estilo padrão que o React nos oferece.
Em seguida, digite o seguinte no terminal para instalar as dependências que nos ajudarão ao longo do projeto:
npm install firebase moment react-firebase-hooks recoil @ material-ui/core @ material-ui/icons react-router-dom
O firebase nos ajuda a interagir com os serviços do Firebase com facilidade, e o moment nos ajuda com datas em nosso projeto. Como estamos usando componentes React baseados em função, react-firebase-hooks fornece vários ganchos que nos informam sobre o estado do usuário.
recoil é uma biblioteca de gerenciamento de estado tão simples quanto Redux, então decidi que poderíamos usar isso e manter as coisas simples.
@ material-ui/core e @ material-ui/icons nos fornecem vários componentes pré-construídos e ícones SVG . Por fim, react-router-dom nos ajuda com as rotas.
Configurando o Firebase
Usaremos o Firebase como back-end, portanto, tenha os detalhes da sua conta do Google prontos. Agora, vá para Firebase console e faça login. Agora, clique em Adicionar projeto e você deverá ver a seguinte tela:
Insira um nome para o projeto. No meu caso, usarei google-classroom-clone-article .
Agora você será perguntado se deseja ativar o Google Analytics para o seu projeto. Embora realmente não precisemos do Google Analytics, não há mal nenhum em mantê-lo ativado. Lembre-se de escolher Conta padrão para Firebase quando for solicitado a escolher uma conta. Clique em Criar projeto .
Agora o Firebase alocará recursos para o projeto. Assim que terminar, pressione Continuar para prosseguir para o painel do projeto. Agora, vamos configurar algumas coisas no painel do Firebase.
Habilitando a autenticação
Na barra lateral, clique em Autenticação e você verá a seguinte tela:
Clique em Começar . Isso habilitará o módulo de autenticação para você e deverá ver as várias opções de autenticação disponíveis:
Neste, vamos usar a autenticação do Google, então clique em Google , pressione Ativar , preencha os detalhes necessários e clique em Salvar .
Você configurou com êxito a autenticação do Google em seu Firebase projeto.
Habilitando o Cloud Firestore
O Cloud Firestore do Firebase é um banco de dados não relacional como o MongoDB. Para ativar o Cloud Firestore, clique em Firestore Database na barra lateral:
Clique em Criar banco de dados e será solicitado o seguinte modal:
Lembre-se de iniciar o banco de dados Firestore no modo de teste . Isso ocorre porque não queremos nos preocupar com um ambiente de produção e regras de segurança, a fim de nos concentrarmos mais no lado do desenvolvimento das coisas. No entanto, você pode alterá-lo para o modo de produção após concluir o projeto.
Clique em Avançar , escolha um local de banco de dados e pressione Criar . Isso finalmente inicializará seu banco de dados do Cloud Firestore.
Agora, vamos copiar nossa configuração do Firebase. Clique no ícone de engrenagem na barra lateral e vá para configurações do projeto . Role para baixo e você verá esta seção:
Clique no terceiro ícone ( > ), que representa um aplicativo da web. Forneça um nome para o aplicativo e clique em Registrar aplicativo . Ignore todas as outras etapas, faremos isso manualmente.
Agora, retorne às configurações do projeto e copie a configuração. Deve ser semelhante a:
Vinculando um aplicativo React ao Firebase
Agora que tudo está configurado, finalmente chegamos à parte divertida da codificação! Em seu aplicativo React, crie um novo arquivo denominado firebase.js e importe o pacote firebase usando esta instrução:
import firebase from”firebase”;
Agora cole na configuração. Precisamos inicializar o aplicativo para que nosso aplicativo React possa se comunicar com o Firebase. Para fazer isso, use o seguinte código:
const app=firebase.initializeApp (firebaseConfig); const auth=app.auth (); const db=app.firestore ();
No código acima, estamos iniciando uma conexão com o Firebase usando a função initializeApp (). Em seguida, extraímos os módulos auth e firestore de nosso aplicativo e os armazenamos em variáveis separadas.
Agora, vamos configurar algumas funções para nos ajudar no aplicativo:
const googleProvider=new firebase.auth.GoogleAuthProvider ();//Faça login e verifique ou crie uma conta no firestore const signInWithGoogle=async ()=> {try {const response=await auth.signInWithPopup (googleProvider); console.log (response.user); const user=response.user; console.log (`ID do usuário-$ {user.uid}`); const querySnapshot=await db.collection (“usuários”).where (“uid”,”==”, user.uid).get (); if (querySnapshot.docs.length===0) {//criar um novo usuário await db.collection (“usuários”). add ({uid: user.uid, matriculadoClassrooms: [],}); }} catch (err) {alert (err.message); }}; const logout=()=> {auth.signOut (); };
No código acima, estamos obtendo o GoogleAuthProvider fornecido pelo Firebase para nos ajudar com a autenticação do Google. Se houver algum erro na autenticação, o usuário será automaticamente transferido para o bloco catch onde o erro será exibido na tela.
Estamos usando a função signInWithPopup () do Firebase e passando o Provedor do Google para informar ao Firebase que queremos fazer login por meio de um provedor externo. Neste caso, é o Google.
Em seguida, estamos verificando nosso banco de dados Firestore para ver se o usuário autenticado existe ou não em nosso banco de dados. Caso contrário, criamos uma nova entrada em nosso banco de dados para considerar o usuário registrado.
O Firebase é muito inteligente para lidar com o armazenamento local. A autenticação persistirá no recarregamento da página, e o Firebase cuida disso nos bastidores. Portanto, não precisamos fazer mais nada depois que o usuário for autenticado.
Agora, criamos uma função logout ().
Finalmente, exporte esses módulos para que possamos usá-los em todo o aplicativo:
export {app, auth, db, signInWithGoogle, logout};
Agora, vamos prosseguir com a configuração do roteador.
Configurando o roteador React
Precisamos configurar nosso aplicativo React para que ele possa lidar com várias rotas com várias telas. react-router-dom nos ajuda com isso.
Vá para App.js e importe os componentes necessários deste pacote:
import {BrowserRouter as Router, Route, Switch} from”react-router-dom”;
Agora, dentro do
Você provavelmente já trabalhou com React Router antes porque ele é usado em quase todos os projetos de várias páginas. Mas se você não sabe sobre isso, não se preocupe, vamos analisar o código acima e ver o que está acontecendo aqui.
Todo o seu código deve ser colocado em
Os componentes incluídos em
Agora , se você notar, poderá ver Hello na tela. Mas se você alterar o URL para algo como http://localhost: 3000/test , verá que Hello não aparece mais. Esse é o poder do React Router.
Criando um novo componente
Vamos fazer um novo componente. Eu recomendo a instalação da extensão ES7 React Snippets no VS Code. Isso o ajudará a criar componentes do React com muita facilidade.
Crie uma nova pasta chamada telas e faça dois arquivos chamados Home.js e Home.css. Vá para Home.js, comece a digitar rfce e pressione Enter . Um novo componente React será feito. Importe o arquivo CSS incluindo esta instrução no topo:
import”./Home.css”;
Sempre usaremos esse método para criar componentes. Vamos voltar ao App.js e adicionar a página inicial à nossa rota inicial. Não se esqueça de importar o componente assim ou você encontrará erros:
import Home from”./components/Home”;
Seu JSX em App.js deve ter a seguinte aparência:
Agora vá para Home.js e adicione o seguinte layout:

Não vamos nos concentrar em estilos neste tutorial, então use o seguinte CSS para o arquivo Home.css:
.home {height: 100vh; largura: 100vw; display: flex; alinhar-itens: centro; justificar o conteúdo: centro; }.home__container {background-color: # f5f5f5; sombra da caixa: 0px 0px 2px-1px preto; display: flex; direção flexível: coluna; preenchimento: 30px; }.home__login {margin-top: 30px; preenchimento: 20px; cor de fundo: # 2980b9; tamanho da fonte: 18px; cor branca; fronteira: nenhum; transformação de texto: maiúsculas; raio da borda: 10px; }
Depois de salvo, você deverá ver uma tela como esta:
Implementando a funcionalidade de login do Google
Já criamos a função em firebase.js para autenticação de identificador ; agora podemos implementá-lo.
Adicione a função signInWithGoogle ao método onClick do botão “Faça login com o Google”. Não se esqueça de importar a função signInWithGoogle. Agora, seu JSX deve ter a seguinte aparência:

Quando um usuário faz login, queremos redirecioná-lo para o painel com os ganchos React Router e Firebase. Os ganchos do Firebase sempre monitoram se o estado de autenticação do usuário mudou, então podemos usar esses dados para verificar se o usuário está conectado. Vamos importar o Firebase e os ganchos do roteador como:
import {useAuthState} from”react-firebase-hooks/auth”; import {useHistory} de”react-router-dom”;
Então, em seu componente, adicione as seguintes linhas para usar os ganchos:
const [user, loading, error]=useAuthState (auth); histórico const=useHistory ();
A primeira linha fornece o estado do usuário. Portanto, se o usuário estiver no estado de carregamento, o carregamento é verdadeiro. Se o usuário não estiver logado, o usuário ficará indefinido ou não haverá dados do usuário. Se houver algum erro, ele será armazenado por engano.
O histórico agora nos dá acesso para rotear o usuário por meio de nosso código sem que ele clique em um link. Podemos usar métodos como history.push () e history.replace () para gerenciar o roteamento.
Finalmente, vamos fazer um gancho useEffect () que redirecionará o usuário assim que ele for autenticado:
useEffect (()=> {if (carregando) return; if (usuário) history.push (“/painel”);}, [carregando, usuário]);
O código acima verifica se o usuário está conectado sempre que o estado do usuário muda. Nesse caso, ele é redirecionado para a rota/painel de controle. Usei useEffect () porque posso verificar o estado do usuário sempre que os estados de autenticação são atualizados. Portanto, se um usuário que está conectado visitar a página inicial, ele será redirecionado para o painel imediatamente, sem mostrar a tela de login.
Agora, se você tentar fazer login com sua conta do Google, verá uma tela em branco porque ainda não temos um painel. Mas antes de criar um painel de controle, criaremos uma barra de navegação, que será comum na maioria de nossas telas.
Criando uma barra de navegação
Crie uma nova pasta no diretório src chamada componentes, em seguida, crie um novo componente chamado Navbar junto com os arquivos JS e CSS. Coloque a Navbar em nossa rota/dashboard em App.js assim:
Agora, se você estiver conectado, o n componente avbar é colocado. Vamos adicionar o layout básico. Primeiro, adicione os ganchos do Firebase, pois precisaremos deles para buscar os dados do usuário:
const [user, loading, error]=useAuthState (auth);
Seu arquivo deve ser semelhante a este:
import {Avatar, IconButton, MenuItem, Menu} de”@ material-ui/core”; importe {Add, Apps, Menu as MenuIcon} de”@ material-ui/icons”; import React, {useState} de”react”; import {useAuthState} de”react-firebase-hooks/auth”; import {auth, logout} de”../firebase”; import”./Navbar.css”; função Navbar () {const [usuário, carregamento, erro]=useAuthState (autenticação); const [anchorEl, setAnchorEl]=useState (nulo); const handleClick=(evento)=> {setAnchorEl (event.currentTarget); }; const handleClose=()=> {setAnchorEl (null); }; return (<>
>); } exportar barra de navegação padrão;
Adicione o seguinte estilo a Navbar.css:
.navbar {width: 100vw; altura: 65px; borda inferior: 1px sólido #dcdcdc; display: flex; justify-content: espaço entre; preenchimento: 0 20px; alinhar-itens: centro; }.navbar__left {display: flex; alinhar-itens: centro; }.navbar__left img {margin-right: 20px; margem esquerda: 20px; }.navbar__left span {font-size: 20px; }.navbar__right {display: flex; alinhar-itens: centro; }.navbar__logo {height: 30px; largura: automático; }
Usamos componentes de IU de material junto com meu próprio estilo, então sua barra de navegação deve ser parecida com esta:
Se você clicar no ícone + , verá um menu pop-up:
Por enquanto, clicar em qualquer uma das opções não fará nada. Vamos fazer novos componentes que funcionarão como modais para criar e ingressar em uma classe. Para isso, precisamos de gerenciamento de estado para determinar se o modal está aberto ou fechado.
Temos muitos componentes, então a biblioteca de gerenciamento de estado de recuo é usada para elevar dados para cada componente acessar. Crie uma nova pasta em src chamada utils e crie um novo arquivo chamado atoms.js. Este arquivo deve se parecer com o seguinte:
import {atom} from”recoil”; const joinDialogAtom=atom ({chave:”joinDialogAtom”, padrão: false,}); const createDialogAtom=atom ({chave:”createDialogAtom”, padrão: false,}); exportar {createDialogAtom, joinDialogAtom};
Os átomos são apenas espaços para armazenar seus dados globais. Aqui, criamos dois átomos globais que indicam se os modais de “junção” ou “criação” estão abertos. Por padrão, eles são sempre falsos.
Agora vamos trabalhar na criação de uma classe.
Criando uma classe
Crie um novo componente na pasta de componentes chamado CreateClass. Não precisamos de um arquivo CSS para este porque estamos usando componentes de IU de material:
import {Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField,} from”@ material-ui/core”; import React, {useState} de”react”; import {useAuthState} de”react-firebase-hooks/auth”; import {useRecoilState} de”recuo”; import {auth, db} de”../firebase”; import {createDialogAtom} de”../utils/atoms”; função CreateClass () {const [usuário, carregamento, erro]=useAuthState (autenticação); const [abrir, setOpen]=useRecoilState (createDialogAtom); const [className, setClassName]=useState (“”); const handleClose=()=> {setOpen (false); }; return (
); } exporta CreateClass padrão;
Aqui, estamos importando nossos átomos de Recoil e recuperando os dados dentro deles, que, em nosso caso, é um booleano que informa se nosso modal está aberto ou não.
Também estamos sincronizando um estado com a caixa de texto para que possamos capturar dados dele a qualquer momento.
O prop aberto em
Temos dois botões que fecharão o modal definindo nosso estado aberto como falso.
Agora, vamos criar uma função que criará uma classe entrando em contato com nosso banco de dados Cloud Firestore:
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 (“usuários”).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, nome: className, creatorName: user.displayName, creatorPhoto: user.photoURL,}); const docRef=await db.collection (“usuários”). doc (docId); esperar docRef.update ({matriculadoClassrooms: userClasses,}); handleClose (); alerta (“Sala de aula criada com sucesso!”); } catch (err) {alert (`Não é possível criar classe-$ {err.message}`); }};
Nesta função, estamos usando um bloco try-catch para que possamos lidar com quaisquer erros capturados ao entrar em contato com o Firebase.
Estamos criando uma nova entrada nas classes de coleção com os dados que obtemos de nosso estado da caixa de texto e ganchos do Firebase, em seguida, obtendo os dados do usuário por meio do nosso banco de dados usando o ID do usuário.
Também estamos adicionando o ID da classe ao array matriculadoClasses do nosso usuário e atualizando os dados do usuário no banco de dados.
Agora insira esta função no onClick do botão Criar . Seu arquivo JS deve ter a seguinte aparência:
import {Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField,} from”@ material-ui/core”; import React, {useState} de”react”; import {useAuthState} de”react-firebase-hooks/auth”; import {useRecoilState} de”recuo”; import {auth, db} de”../firebase”; import {createDialogAtom} de”../utils/atoms”; função CreateClass () {const [usuário, carregamento, erro]=useAuthState (autenticação); const [abrir, 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: [],});//adiciona à lista de classes do usuário atual 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, nome: className, creatorName: user.displayName, creatorPhoto: user.photoURL,}); const docRef=await db.collection (“usuários”). doc (docId); esperar docRef.update ({matriculadoClassrooms: userClasses,}); handleClose (); alerta (“Sala de aula criada com sucesso!”); } catch (err) {alert (`Não é possível criar classe-$ {err.message}`); }}; return (
); } exporta CreateClass padrão;
Ingressando em uma classe
O conceito básico de ingressar em uma classe é muito semelhante ao de criar uma classe. Veja como JoinClass.js deve ser:
import {Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField,} from”@ material-ui/core”; import React, {useState} de”react”; import {useAuthState} de”react-firebase-hooks/auth”; import {useRecoilState} de”recuo”; import {auth, db} de”../firebase”; importar {joinDialogAtom} de”../utils/atoms”; função JoinClass () {const [open, setOpen]=useRecoilState (joinDialogAtom); const [usuário, carregamento, erro]=useAuthState (autenticação); const [classId, setClassId]=useState (“”); const handleClose=()=> {setOpen (false); }; const joinClass=async ()=> {try {//verifique se a classe existe const classRef=await db.collection (“classes”). doc (classId).get (); if (! classRef.exists) {return alert (`A classe não existe, forneça o ID correto`); } const classData=await classRef.data ();//adiciona classe ao usuário 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 ({matriculadoClassrooms: tempClassrooms,});//alert done alert (`Inscrito em $ {classData.name} com sucesso!`); handleClose (); } catch (errar) {console.error (errar); alerta (err.mensagem); }}; return (
); } exporta JoinClass padrão;
A diferença aqui é que estamos usando o outro átomo, que verifica se o modal “juntar classe” está aberto ou não, e se a classe existe. Em caso afirmativo, nós o adicionamos ao array registeredClasses do usuário e atualizamos o usuário no Firestore. É tão simples!
Agora, precisamos vincular tudo na barra de navegação e definir a função onClick. Veja como seu arquivo Navbar.js deve ficar:
import {Avatar, IconButton, MenuItem, Menu} de”@ material-ui/core”; importe {Add, Apps, Menu as MenuIcon} de”@ material-ui/icons”; import React, {useState} de”react”; import {useAuthState} de”react-firebase-hooks/auth”; import {useRecoilState} de”recuo”; import {auth, logout} de”../firebase”; import {createDialogAtom, joinDialogAtom} de”../utils/atoms”; importar CreateClass de”./CreateClass”; importar JoinClass de”./JoinClass”; import”./Navbar.css”; função Navbar () {const [usuário, carregamento, erro]=useAuthState (autenticação); const [anchorEl, setAnchorEl]=useState (nulo); const [createOpened, setCreateOpened]=useRecoilState (createDialogAtom); const [joinOpened, setJoinOpened]=useRecoilState (joinDialogAtom); const handleClick=(evento)=> {setAnchorEl (event.currentTarget); }; const handleClose=()=> {setAnchorEl (null); }; return (<>
> ); } export default Navbar;
Creating the dashboard
Now let’s get on the dashboard. Create a new component in the screens folder named Dashboard, remembering to make JS and CSS files for it. Here’s the styling for Dashboard.css:
.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; }
Now, let’s first make a component that will display the individual classes. It will be nothing special, just rendering out data with styles. Create a new component called ClassCard in components and copy this 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 (
); } export default ClassCard;
Here we are just taking in props and rendering it out. One thing to note is that when the user presses on the card component, she gets redirected to the class screen.
Here’s ClassCard.css:
.classCard__upper { background-color: #008d7d; height: 90px; posição: relativa; 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; }
Now, let’s include the Dashboard component into our App.js file:
Now open Dashboard.js and make a function to fetch all of the user’s classes:
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); } };
This code is similar to when we fetched data from Firestore. We set up a snapshot listener so that whenever data updates in the Firestore database, the changes are reflected here.
Now that we have the classes in a state, we can easily render them out. Here’s 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 (
): (
)}
); } export default Dashboard;
Now, if you create a few classes, you should see them populate on the page:
Congrats! Our dashboard is ready. Now we need to make a class screen where all the announcements from each class will be displayed.
Creating the class screen
First let’s create a component that will help us create the class screen. We need to display announcements from a class, so we make an Announcement component that will take in the props and render out 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 (
); } export default Announcement;
Nothing much happening here, just basic layouts. 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; }
Now, let’s create the class screen. Create a new component named Class in the screens folder. Let’s include it in our App.js:
One thing to note here is:id, a query parameter we are sending through the URL. We can access this id in our class screen, thanks to React Router. Here’s the content for 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; intensidade da fonte: Negrito; font-size: 43px; }.class__announce { display: flex; align-items: center; largura: 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; largura: 100%; margin-left: 20px; margin-right: 20px; font-size: 17px; outline: none; }
Now let’s focus on Class.js. Again, it’s the same as what we did with components before:
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 (
{posts?.map((post)=> (
); } export default Class;
There are a lot of things going on here, let’s break it down.
We are setting up a snapshot listener in one of the useEffect() hooks so that we get existing posts from the database. Then, we reverse the array and save it in another state. Reversing the posts will give us newest posts on top.
We are rendering the Announcement component according to posts. Once a person creates an announcement, the posts array is fetched from the database, a new entry is added, and the data is updated on database.
Because we have a snapshot listener, whenever we create a post, it will automatically get updated on the screen.
The class ID is in the URL bar. Other users can use the ID to join the class.
If everything is set up correctly, you should see something like this after adding a few posts:
Congrats! What’s next?
You’ve successfully made a Google Classroom clone using React and Firebase! Now you can play around with the code – try new things such as editing posts, or adding comments and attachments.
I’d also recommend making different clones. Exercises like this will help you understand how these popular apps work on a deeper level.
If you need the code of this clone, check out my GitHub repository where I’ve pushed all the code. You can make pull requests if you want to add more features.