Article original : How to Build a Google Docs Clone with React, Material UI, & Firebase
Par Nishant Kumar
Dans cet article, nous allons créer un clone de Google Docs en utilisant React, Material UI et Firebase.
L'application finale ressemblera à ceci :

Si nous cliquons sur un document, il s'ouvrira et nous pourrons les modifier comme nous le souhaitons.

Et la fonctionnalité la plus incroyable est que nous pouvons éditer un document en temps réel. Cela signifie que si deux personnes travaillent sur le même document, leurs progrès seront reflétés dans les deux instances.
Mais avant de commencer, assurez-vous d'avoir Node installé dans votre système. Si ce n'est pas le cas, rendez-vous sur https://nodejs.org/en/download/ pour le télécharger et l'installer.
Si vous souhaitez suivre ce tutoriel en format vidéo, voici la vidéo sur ma chaîne YouTube :
Configuration de base du projet
Commençons par créer une application React en utilisant la commande suivante :
npx create-react-app google-docs-clone
Cela installera tous les packages et dépendances dans un dossier local.
Ensuite, naviguez simplement dans le dossier du projet et exécutez npm start pour lancer l'application.

Nous verrons tout ce code ici que nous devons supprimer. Nous commencerons avec une toile vierge.
Ensuite, créez un dossier appelé components. À l'intérieur de ce dossier, créons un fichier appelé docs.js.

Faites de ce composant un composant fonctionnel, comme ceci :
import React from 'react'
export default function Docs() {
return (
<div>
<h1>docs</h1>
</div>
)
}
Maintenant, importez ce fichier dans le fichier principal App.js.
import './App.css';
import Docs from './components/docs';
function App() {
return (
<Docs />
);
}
export default App;
Et nous verrons cette sortie à l'écran :
Clone de Google docs montrant la sortie "docs" dans le coin supérieur gauche
Maintenant, faisons apparaître le titre au centre. Donc dans docs.js, donnez à la div principale une className de docs-main.
import React from 'react'
export default function Docs() {
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
</div>
)
}
Et dans le fichier App.css, ajoutez les styles suivants :
.docs-main{
text-align: center;
}
Maintenant, notre application ressemble à ceci :
Clone de Google docs avec le titre au centre
Maintenant, nous avons besoin d'un bouton pour ajouter nos documents. Donc, créons-le avec ce code :
import React from 'react'
export default function Docs() {
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
<button className='add-docs'>
Ajouter un Document
</button>
</div>
)
}
Et le CSS ressemble à ceci :
.add-docs{
height: 40px;
width: 200px;
background-color: #ffc107;
border: none;
cursor: pointer;
}
Importons quelques polices de Google Fonts. Placez ceci en haut du fichier CSS :
@import url('https://fonts.googleapis.com/css2?family=Poppins&family=Roboto&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Poppins&family=Roboto&display=swap');
.docs-main{
text-align: center;
font-family: 'Roboto', sans-serif;
}
.add-docs{
height: 40px;
width: 200px;
background-color: #ffc107;
border: none;
cursor: pointer;
font-family: 'Poppins', sans-serif;
}
Pour ajouter des polices, faites simplement ceci dans les classNames respectives.
Comment installer Material UI
Pour installer Material UI, tapez simplement la commande ci-dessous. Si vous souhaitez lire la documentation, rendez-vous sur https://mui.com/.
npm install @mui/material @emotion/react @emotion/styled
Maintenant, créons un autre composant pour la modale. Nous utiliserons cette modale pour ajouter des documents à la base de données Firebase.
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function Modal() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Texte dans une modale
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
</div>
);
}
Ceci est un simple composant de modale de Material UI. Maintenant, nous devons importer ce composant dans notre composant Docs.js.
Et nous devons déplacer quelques éléments de Modal.js vers Docs.js.
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
Si nous cliquons sur le bouton Ajouter un Document, la modale s'ouvrira en utilisant ces fonctions :
import React, { useState } from 'react';
import Modal from './Modal';
export default function Docs() {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
<button
className='add-docs'
onClick={handleOpen}
>
Ajouter un Document
</button>
<Modal
open={open}
setOpen={setOpen}
/>
</div>
)
}
Donc, passez ces fonctions et états en tant que props dans le composant de modale et recevez-les.
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function ModalComponent({
open,
setOpen,
}) {
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Texte dans une modale
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
</div>
);
}
Maintenant, voici à quoi ressemble notre page avec la modale :
Page du clone de Google docs avec la modale affichée
Ajoutons une entrée dans la modale, pour le nom du fichier.
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Ajouter le Titre'
className='add-input'
/>
</Box>
</Modal>
Donnons-lui quelques styles avec ce qui suit :
.add-input{
width: 95%;
height: 40px;
outline: none;
border: 1px solid #676767;
border-radius: 0px;
padding: 10px;
font-family: 'Poppins', sans-serif;
}
Et maintenant, voici à quoi ressemble notre modale :
Modale avec le style ajouté
Ajoutons également un bouton. Nous pouvons copier le bouton Ajouter un Document.
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 500,
height: 150,
bgcolor: 'background.paper',
boxShadow: 24,
p: 5,
};
export default function ModalComponent({
open,
setOpen,
}) {
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Ajouter le Titre'
className='add-input'
/>
<div className='button-container'>
<button
className='add-docs'
>
Ajouter
</button>
</div>
</Box>
</Modal>
</div>
);
}
Et le CSS ressemble à ceci :
.button-container{
text-align: center;
margin: 30px;
}
Voici à quoi cela ressemble maintenant :
Modale avec le style et le bouton ajoutés
Comment ajouter Firebase à notre application
Maintenant, installons Firebase pour la base de données. Installez simplement Firebase en utilisant la commande ci-dessous :
npm install firebase
Rendez-vous sur https://firebase.google.com/ et cliquez sur Go to console en haut à droite.

Ensuite, cliquez sur Ajouter un projet.

Après avoir créé le projet, cliquez sur le bouton code pour créer une application web dans Firebase. Donnez-lui un nom et nous sommes prêts à partir.

Maintenant, nous allons ajouter toutes ces données de configuration que nous devons stocker dans notre application React. Donc, créez un fichier appelé firebaseConfig.js et ajoutez-les.

Nous allons avoir besoin de la base de données, donc initialisons-la. Exportons également la const app et la base de données comme ceci :
import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
//Vos données Firebase
};
export const app = initializeApp(firebaseConfig);
export const database = getFirestore(app)
Importez l'application et la base de données dans le fichier App.js. Et passez la base de données en tant que props au composant Docs. Nous l'utiliserons plus tard pour ajouter des données à Firebase Firestore.
import './App.css';
import Docs from './components/docs';
import { app, database } from './firebaseConfig';
function App() {
return (
<Docs database={database}/>
);
}
export default App;
Et dans le composant Docs. Recevons également l'export de la base de données depuis les props.
import React, { useState } from 'react';
import Modal from './Modal';
export default function Docs({
database
}) {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
<button
className='add-docs'
onClick={handleOpen}
>
Ajouter un Document
</button>
<Modal
open={open}
setOpen={setOpen}
/>
</div>
)
}
Maintenant, configurons notre base de données Firestore.
Allez dans la base de données Firestore depuis la barre latérale de gauche, et cliquez sur Créer une base de données.

Nous allons démarrer notre base de données en mode Production. Donc, cliquez sur Suivant, puis sur Activer.

Nous devons rendre les règles de sécurité publiques, juste pour l'instant. Donc, cliquez sur Règles dans l'onglet supérieur et modifiez les règles suivantes. Cela signifie que n'importe qui peut écrire des données ou les lire, même sans authentification.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
Comment ajouter des données de documents à la base de données Firestore
Maintenant, ajoutons réellement nos données. Mais avant cela, nous devons obtenir les données du champ d'entrée.
Donc dans le composant Docs, créez un état qui contiendra ces données.
const [title, setTitle] = useState('')
Passez le titre et setTitle au composant modal.
<Modal
open={open}
setOpen={setOpen}
title={title}
setTitle={setTitle}
/>
Recevez-les tous les deux en tant que props, et définissez-les dans le champ d'entrée.
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 500,
height: 150,
bgcolor: 'background.paper',
boxShadow: 24,
p: 5,
};
export default function ModalComponent({
open,
setOpen,
title,
setTitle
}) {
const handleClose = () => setOpen(false);
return (
<div>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Ajouter le Titre'
className='add-input'
onChange={(event) => setTitle(event.target.value)}
value={title}
/>
<div className='button-container'>
<button
className='add-docs'
>
Ajouter
</button>
</div>
</Box>
</Modal>
</div>
);
}
Maintenant, si nous tapons quelque chose dans l'entrée, il sera stocké dans l'état title.
Ensuite, nous avons besoin d'une fonction qui déclenchera les fonctions d'ajout de données, alors créons-la.
Dans Docs.js, créez une fonction et passez-la au composant modal :
const addData = () => {
}
Recevez-la dans le composant modal et liez-la simplement au bouton Ajouter comme ceci :
<div className='button-container'>
<button
className='add-docs'
onClick={addData}
>
Ajouter
</button>
</div>
Maintenant, la fonction addData s'exécutera lorsque nous cliquerons sur le bouton Ajouter.
Maintenant, pour envoyer des données de React à Firebase dynamiquement, importons quelques éléments de Firebase Firestore :
import { addDoc, collection } from 'firebase/firestore';
Ici, nous utiliserons collection pour créer une collection de données dans Firebase, et addDoc ajoutera des données à cette collection.
Créons d'abord une référence de collection. Elle prendra la base de données que nous avons obtenue de firebaseConfig.js et le nom de la collection que nous voulons utiliser.
const collectionRef = collection(database, 'docsData')
Maintenant, dans la fonction addData, utilisons addDoc. Cette fonction addDoc prendra la référence de collection, et les données elles-mêmes.
const addData = () => {
addDoc(collectionRef, {
title: title
})
.then(() => {
alert('Données ajoutées')
})
.catch(() => {
alert('Impossible d'ajouter les données')
})
}
Maintenant, ajoutez quelque chose dans le champ de texte et cliquez sur Ajouter. Il sera ajouté dans Firebase Firestore, avec une alerte indiquant que les données ont été ajoutées. Mais si cela échoue, nous obtiendrons "Impossible d'ajouter les données".

Si nous actualisons la base de données, nous verrons cette nouvelle entrée :

Et c'est ainsi que nous ajoutons des données. Fermons également la modale après avoir ajouté les données.
Créez une fonction handleClose, et appelez cette fonction juste après le bloc then dans la fonction addData.
const addData = () => {
addDoc(collectionRef, {
title: title
})
.then(() => {
alert('Données ajoutées');
handleClose()
})
.catch(() => {
alert('Impossible d'ajouter les données')
})
}
Comment lire les données de Firebase
Maintenant, lisons les données que nous avons ajoutées à Firebase. Nous aurons besoin de la fonction onSnapshot pour cela. La fonction onSnapshot obtient les données en temps réel.
Tout d'abord, importez-la de Firebase comme ceci :
import { addDoc, collection, onSnapshot } from 'firebase/firestore';
Ensuite, créez une fonction getData qui sera déclenchée lorsque notre page se chargera. Donc, nous mettrons cet onSnapshot dans le Hook useEffect de React.
const getData = () => {
onSnapshot(collectionRef, (data) => {
console.log(data.docs.map((doc) => {
return {...doc.data(), id: doc.id}
}))
})
}
Ensuite, appelez cette fonction à l'intérieur du Hook useEffect.
useEffect(() => {
getData()
}, [])

Mais comme vous pouvez le voir, nous obtenons les données deux fois. C'est parce que nous utilisons React version 18, qui inclut le concurrent rendering. C'est pourquoi le hook useEffect s'exécutera deux fois.
Pour résoudre ce problème, nous devons créer une référence useRef.
const isMounted = useRef()
Ensuite, dans le Hook useEffect, nous devons vérifier si isMounted.current est vrai. Donc, si c'est vrai, nous ne retournerons rien. Et ensuite, nous définirons isMounted.current à vrai, et nous appellerons notre fonction getData.
useEffect(() => {
if(isMounted.current){
return
}
isMounted.current = true;
getData()
}, [])
Et si nous actualisons maintenant la page, nous obtiendrons les données une seule fois.

Maintenant, nous devons inclure ces données dans un état de tableau. Donc, faisons cela.
Créez un état de docsData.
const [docsData, setDocsData] = useState([]);
Et définissez les données entrantes à l'intérieur de cet état en utilisant setDocsData.
const getData = () => {
onSnapshot(collectionRef, (data) => {
setDocsData(data.docs.map((doc) => {
return {...doc.data(), id: doc.id}
}))
})
}
Maintenant, mappons notre tableau pour que les données s'affichent dans l'UI.
<div>
{docsData.map((doc) => {
return (
<div>
<p>{doc.title}</p>
</div>
)
})}
</div>
Cela affichera toutes les données dans notre application React.

Nous verrons les deux documents sur notre page. Mais faisons-les apparaître dans une grille. Donnez aux conteneurs div les classNames de grid-main et grid-child.
<div className='grid-main'>
{docsData.map((doc) => {
return (
<div className='grid-child'>
<p>{doc.title}</p>
</div>
)
})}
</div>
Et dans le CSS, ajoutez les classes suivantes :
.grid-main{
display: grid;
grid-template-columns: auto auto auto auto;
color: whitesmoke;
margin-top: 20px;
gap: 20px;
justify-content: center;
}
.grid-child{
padding: 20px;
background-color: rgb(98, 98, 98);
width: 300px;
cursor: pointer;
}
Maintenant, notre application ressemblera à ceci :

Comment obtenir l'ID et rediriger vers la page d'édition des documents
Maintenant, chacun de ces éléments ci-dessus a un ID. Nous utiliserons ces ID pour rediriger vers une autre page où nous pouvons éditer les éléments et écrire notre contenu principal.
Pour cela, nous avons besoin de deux packages. L'un est React-Router pour nous rediriger, et l'autre est React-Quill pour notre éditeur. Installez-les comme ceci :
npm i react-quill react-router-dom@6
Maintenant, configurons le routage vers une autre page. Mais nous avons besoin d'une autre page d'abord. Donc, créons-la.
Créez un composant appelé EditDocs. Faites-en un composant fonctionnel.
import React from 'react'
export default function EditDocs() {
return (
<div>EditDocs</div>
)
}
Pour configurer le routage, allez dans index.js, le point d'entrée de l'application. Enveloppez le à l'intérieur de BrowserRouter.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Maintenant, nous pouvons utiliser le routage n'importe où car nous déclarons BrowserRouter au niveau de base.
Maintenant, allez dans le fichier App.js. Importez Routes et Route de React-Router. Nous ajoutons également l'ID dans le chemin editDocs, afin que nous puissions voir l'ID dans la barre d'adresse.
import { Routes, Route } from "react-router-dom";
import './App.css';
import Docs from './components/docs';
import EditDocs from './components/EditDocs';
import { Routes, Route } from "react-router-dom";
import { app, database } from './firebaseConfig';
function App() {
return (
<Routes>
<Route path="/" element={<Docs database={database} />} />
<Route path="/editDocs/:id" element={<EditDocs database={database}/>} />
</Routes>
);
}
export default App;
Et ajoutez les routes suivantes. Si nous allons à '/editDocs/:id', nous verrons notre page editDocs.

Maintenant, nous devons obtenir l'ID spécifique des documents et l'envoyer à la page editDocs.
Créez une fonction getID et attribuez la fonction aux documents.
const getID = () => {
}
<div className='grid-main'>
{docsData.map((doc) => {
return (
<div className='grid-child' onClick={() => getID(doc.id)}>
<p>{doc.title}</p>
</div>
)
})}
</div>
Maintenant, si nous cliquons sur le document, nous obtiendrons son ID si nous le journalisons dans la console.
const getID = (id) => {
console.log(id)
}

Maintenant, envoyons cet ID à la page editDocs en utilisant useNavigate.
Tout d'abord, importez useNavigate de react-router.
import { useNavigate } from 'react-router-dom';
Ensuite, créez une instance de useNavigate comme ceci :
let navigate = useNavigate();
Ensuite, pour passer l'ID, faites simplement ceci. Nous nous enverrons à la page editDocs, avec l'ID.
const getID = (id) => {
navigate(`/editDocs/${id}`)
}
Maintenant, recevons notre ID à l'autre extrémité. Dans le composant editDocs, nous avons besoin de useParams de react-router.
Donc, importez-le et créez une instance :
import { useParams } from 'react-router-dom';
let params = useParams();
Aussi, si nous le journalisons, nous verrons l'ID.
import { useParams } from 'react-router-dom';
let params = useParams();
console.log(params)

Nous pouvons voir que nous obtenons l'ID dans la barre d'adresse ainsi que dans la console.
Maintenant, ajoutons React Quill à notre page editDocs.
import React from 'react';
import { useParams } from 'react-router-dom';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
export default function EditDocs() {
let params = useParams();
return (
<div>
<h1>EditDocs</h1>
<ReactQuill />
</div>
)
}
Nous devons importer React-Quill et le CSS.

Mais nous pouvons voir que nous avons deux barres d'outils ici. Pour résoudre ce problème, supprimez simplement React.StrictMode de index.js.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Et tout ira bien.

Maintenant, nous avons besoin d'un état pour ces données React Quill. Donc, créons-le. Nous créerons également une fonction pour déclencher lorsque nous tapons.
const [docsDesc, setDocsDesc] = useState('');
const getQuillData = () => {
}
Maintenant, attachons la fonction et l'état à React Quill.
<ReactQuill
value={docsDesc}
onChange={getQuillData}
/>
Dans la fonction getQuillData, attachons la valeur à l'état docsDesc, en utilisant la fonction setDocsDesc.
const getQuillData = (value) => {
setDocsDesc(value)
}
Et nous avons terminé ici. Vous pouvez journaliser cet état docsDesc pour vérifier.
Maintenant, nous avons l'ID et les données que nous pouvons utiliser pour mettre à jour le document. Donc, faisons cela.
Comment mettre à jour le document
Nous avons besoin de deux choses, updateDoc et la fonction collection. Nous utiliserons une fonction Debounce pour appeler la fonction updateDoc. Cela signifie que lorsque nous aurons fini de taper, après 5 ou 10 secondes, notre fonction updateDoc s'exécutera.
Donc, créons une fonction :
const updateDocsData = () => {
}
Nous devons également spécifier la collection. Pour cela, nous avons besoin de la database de App.js. Donc, obtenons-la en utilisant les props.
<Route path="/editDocs/:id" element={<EditDocs database={database}/>} />
Maintenant, créons une référence de collection.
const collectionRef = collection(database, 'docsData')
Maintenant pour le debouncing, nous avons besoin de updateDocsData dans un hook useEffect.
useEffect(() => {
const updateDocsData = () => {
}
}, [])
Maintenant, ajoutons une fonction setTimeout avec un intervalle. Cela signifie que la fonction s'exécutera après cet intervalle spécifié. Faites l'intervalle 1000 millisecondes, ou 1 seconde.
useEffect(() => {
const updateDocsData = setTimeout(() => {
}, 1000)
return () => clearTimeout(updateDocsData)
}, [])
Maintenant, à l'intérieur de setTimeOut, ajoutons la fonction updateDoc. Donc à l'intérieur de la variable document, nous passons collectionRef et l'ID des params. Et ensuite, updateDoc prend cette variable document comme premier paramètre.
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
})
}, 1000)
Importons également la fonction doc. Elle spécifie quel document mettre à jour en utilisant l'ID comme clé primaire.
import {
updateDoc,
collection,
doc
} from 'firebase/firestore';
Maintenant, passons les données dans le deuxième paramètre, dans la fonction updateDoc.
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [])
Dans le tableau de dépendances, ajoutez l'état de docsDesc. Donc après avoir tapé quelque chose, la fonction updateDoc s'exécutera après 1 seconde.
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
alert('Enregistré')
})
.catch(() => {
alert('Impossible d'enregistrer')
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])
Donc, tapez quelque chose dans l'éditeur, et il sera enregistré dans la base de données.

Et les données ici :

Si nous ajoutons quelque chose de plus, nous ajouterons les données précédentes :


Comment récupérer les données de la base de données vers l'éditeur
Maintenant, si nous revenons en arrière et cliquons sur un document, les données seront nulles, ou effacées. Donc, nous devons récupérer les données de la base de données et les définir dans l'éditeur.
Nous utiliserons la fonction onSnapshot pour cela.
import {
updateDoc,
collection,
doc,
onSnapshot
} from 'firebase/firestore';
const getData = () => {
}
useEffect(() => {
if(isMounted.current){
return
}
isMounted.current = true;
getData()
}, [])
Donc, c'est exactement comme nous l'avons fait dans le composant Docs. Nous devons spécifier quelles données récupérer en utilisant le paramètre ID. Et ensuite, nous passons ce document à la fonction onSnapshot pour obtenir les données dont nous avons besoin.
const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
console.log(docs.data().docsDesc)
})
}

Définissons ce docs.data().docsDesc dans l'état docsDesc en utilisant setDocsDesc. Donc, si le document se charge, il sera défini là.
Ajoutez quelques données, puis revenez en arrière. Et si vous revenez au même composant, la description du document sera là.

Maintenant, dans la page d'accueil où nous voyons toutes les données, nous devons également ajouter la description, si elle existe.
<div dangerouslySetInnerHTML={{__html: doc.docsDesc}} />
Nous utilisons dangerouslySetInnerHTML car les données sont ajoutées sous forme de balises dans React Quill. Cela facilite le rendu de la mise en forme.

Voyez, j'ai ajouté quelques mises en forme comme des textes en gras et en italique.
Maintenant, nous devons apporter quelques modifications mineures. Dans le fichier App.js (où nous ajoutons le titre du document), ajoutons également la description, qui sera initialement vide.
const addData = () => {
addDoc(collectionRef, {
title: title,
docsDesc: ''
})
.then(() => {
alert('Données ajoutées');
handleClose()
})
.catch(() => {
alert('Impossible d'ajouter les données')
})
}
Donc, si nous créons un document, nous aurons docsDesc dans le document Firestore. Cela empêchera notre application de planter lorsque nous allons à la page EditDocs.
Maintenant, dans la page EditDocs, ajoutons le titre du document pour qu'il s'affiche en haut. Créez un état appelé documentTitle et définissez-le.
const [documentTitle, setDocumentTitle] = useState('')
const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
setDocumentTitle(docs.data().title)
setDocsDesc(docs.data().docsDesc);
})
}
Et affichez cet état en haut :
<h1>{documentTitle}</h1>
Voici le code complet de la page EditDocs jusqu'à présent :
import React, { useEffect, useState, useRef } from 'react';
import { useParams } from 'react-router-dom';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import {
updateDoc,
collection,
doc,
onSnapshot
} from 'firebase/firestore';
export default function EditDocs({
database
}) {
const isMounted = useRef()
const collectionRef = collection(database, 'docsData')
let params = useParams();
const [documentTitle, setDocumentTitle] = useState('')
const [docsDesc, setDocsDesc] = useState('');
const getQuillData = (value) => {
setDocsDesc(value)
}
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
alert('Enregistré')
})
.catch(() => {
alert('Impossible d'enregistrer')
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])
const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
setDocumentTitle(docs.data().title)
setDocsDesc(docs.data().docsDesc);
})
}
useEffect(() => {
if (isMounted.current) {
return
}
isMounted.current = true;
getData()
}, [])
return (
<div>
<h1>{documentTitle}</h1>
<ReactQuill
value={docsDesc}
onChange={getQuillData}
/>
</div>
)
}
Comment ajouter un peu de style
Maintenant, ajoutons un peu de style à cette page EditDocs :
<div className='editDocs-main'>
<h1>{documentTitle}</h1>
<div className='editDocs-inner'>
<ReactQuill
className='react-quill'
value={docsDesc}
onChange={getQuillData}
/>
</div>
</div>
Et dans le CSS, ajoutez le style suivant :
.editDocs-main {
font-family: 'Poppins', sans-serif;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.editDocs-inner {
width: 800px;
box-shadow: 0px -2px 5px 2px rgba(181, 181, 181, 0.75);
-webkit-box-shadow: 0px -2px 5px 2px rgba(181, 181, 181, 0.75);
-moz-box-shadow: 0px -2px 5px 2px rgba(181, 181, 181, 0.75);
padding: 20px;
height: 750px;
}
.ql-container.ql-snow {
border: none !important;
}
Nous ajoutons une ombre de boîte, nous supprimons la bordure de React Quill, et nous centrons tout.
Voici à quoi ressemble notre page d'édition de document maintenant :

Maintenant, pour notre dernière chose : remplaçons nos alertes par des messages toast. Nous avons besoin d'un autre package appelé React Toastify. Donc, installons-le.
npm i react-toastify
Ensuite, nous devons importer ces deux choses :
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
Et puis, le composant .
Maintenant, pour le message toast, faites simplement ceci :
useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
toast.success('Document enregistré', {
autoClose: 2000
})
})
.catch(() => {
toast.error('Impossible d'enregistrer le document', {
autoClose: 2000
})
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])
Nous avons toast.success pour les alertes de succès, et toast.error pour les alertes d'erreur.

Conclusion
Et voilà, vous avez créé un clone de Google Docs. Vous êtes libre d'expérimenter et d'améliorer cela.
Vous pouvez obtenir le code complet ici : https://github.com/nishant-666/Google-Docs-Clone
Aussi, consultez ma chaîne Cybernatico pour plus de tutoriels incroyables comme celui-ci.
Bon apprentissage.