Skip to main content

Xây dựng bản sao Google Docs

Ngày 9 tháng 5 năm 2022/#Căn cứ hỏa lực

Cách xây dựng bản sao Google Docs bằng React, Material UI và Firebase

Bởi Nishant Kumar

Trong bài viết này, chúng ta sẽ xây dựng bản sao Google Docs bằng React, Material UI và Firebase.

Ứng dụng cuối cùng sẽ trông như thế này:

Hình ảnh

Nếu chúng ta nhấp vào bất kỳ tài liệu nào, nó sẽ mở ra và chúng ta có thể chỉnh sửa chúng theo ý muốn.

Hình ảnh

Và tính năng tuyệt vời nhất là chúng ta có thể chỉnh sửa tài liệu theo thời gian thực. Điều này có nghĩa là nếu hai người đang làm việc trên cùng một tài liệu, tiến trình của họ sẽ được phản ánh trong cả hai trường hợp.

Nhưng trước khi bắt đầu, hãy đảm bảo bạn đã cài đặt Node trong hệ thống của mình. Nếu chưa, hãy truy cập https://nodejs.org/en/download/ để tải xuống và cài đặt.

Nếu bạn muốn theo dõi bằng video, đây là hướng dẫn trên kênh YouTube của tôi:

Thiết lập dự án cơ bản

Trước tiên, hãy tạo một ứng dụng React bằng lệnh bên dưới:

npx create-react-app google-docs-clone

Thao tác này sẽ cài đặt tất cả các gói và phần phụ thuộc vào một thư mục cục bộ.

Sau đó, chỉ cần điều hướng đến thư mục dự án và chạy npm start để chạy ứng dụng.

Hình ảnh

Chúng ta sẽ thấy tất cả các mã này ở đây mà chúng ta cần xóa. Chúng ta sẽ bắt đầu với một canvas trống.

Tiếp theo , tạo một thư mục có tên là components. Bên trong thư mục đó, hãy tạo một tệp có tên là docs.js.

Hình ảnh

Biến thành phần này thành một thành phần chức năng như thế này:

import React from 'react'

export default function Docs() {
return (
<div>
<h1>docs</h1>
</div>
)
}

Bây giờ, hãy nhập tệp này vào tệp App.js chính .

import './App.css';
import Docs from './components/docs';

function App() {
return (
<Docs />
);
}

export default App;

Và chúng ta sẽ thấy kết quả này trên màn hình:

Hình ảnhBản sao Google Docs hiển thị đầu ra "docs" ở góc trên bên trái

Bây giờ, hãy làm cho tiêu đề xuất hiện ở giữa. Vì vậy, trong docs.js, hãy cung cấp cho div chính một className là docs-main .

import React from 'react'

export default function Docs() {
return (
<div className='docs-main'>
<h1>Docs Clone</h1>
</div>
)
}

Và trong tệp App.css , thêm các kiểu sau:

.docs-main{
text-align: center;
}

Bây giờ ứng dụng của chúng ta trông như thế này:

Hình ảnhBản sao Google Docs với tiêu đề ở giữa

Bây giờ, chúng ta cần một nút để thêm tài liệu của mình. Vì vậy, hãy tạo nó bằng mã này:

import React from 'react'

export default function Docs() {
return (
<div className='docs-main'>
<h1>Docs Clone</h1>

<button className='add-docs'>
Add a Document
</button>
</div>
)
}

Và CSS trông như thế này:

.add-docs{
height: 40px;
width: 200px;
background-color: #ffc107;
border: none;
cursor: pointer;
}

Hãy nhập một số phông chữ từ Google Fonts. Đặt phần này ở đầu tệp 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;
}

Để thêm phông chữ, chỉ cần thực hiện thao tác này trong classNames tương ứng.

Cách cài đặt Material UI

Để cài đặt Material UI, chỉ cần nhập lệnh bên dưới. Nếu bạn muốn đọc tài liệu, hãy truy cập https://mui.com/ .

npm install @mui/material @emotion/react @emotion/styled

Bây giờ, hãy tạo thêm một thành phần nữa cho modal. Chúng ta sẽ sử dụng modal này để thêm tài liệu vào cơ sở dữ liệu 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">
Text in a modal
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
</div>
);
}

Đây là một thành phần modal đơn giản từ Material UI. Bây giờ chúng ta phải nhập thành phần này vào thành phần Docs.js của chúng ta.

Và chúng ta cần di chuyển một vài thứ từ Modal.js sang Docs.js.

const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);

Nếu chúng ta nhấp vào nút Thêm tài liệu, hộp thoại sẽ mở ra bằng các hàm sau:

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}
>
Add a Document
</button>

<Modal
open={open}
setOpen={setOpen}
/>
</div>
)
}

Vì vậy, hãy truyền các hàm và trạng thái này dưới dạng props vào thành phần modal và nhận chúng.

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">
Text in a modal
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
</div>
);
}

Bây giờ, đây là giao diện trang của chúng ta với hộp thoại:

Hình ảnhTrang sao chép Google Docs với mô hình hiển thị

Hãy thêm một mục nhập vào Modal để điền tên tệp.

<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<input
placeholder='Add the Title'
className='add-input'
/>
</Box>
</Modal>

Chúng ta hãy thử tạo một số kiểu dáng sau:

.add-input{
width: 95%;
height: 40px;
outline: none;
border: 1px solid #676767;
border-radius: 0px;
padding: 10px;
font-family: 'Poppins', sans-serif;
}

Và bây giờ, Modal của chúng ta trông như thế này:

Hình ảnhModal với kiểu dáng được thêm vào

Chúng ta cũng hãy thêm một Nút. Chúng ta có thể sao chép Nút Thêm Tài liệu.

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='Add the Title'
className='add-input'
/>
<div className='button-container'>
<button
className='add-docs'
>
Add
</button>
</div>
</Box>
</Modal>
</div>
);
}

Và CSS trông như thế này:

.button-container{
text-align: center;
margin: 30px;
}

Nó trông như thế này bây giờ:

Hình ảnhModal có kiểu dáng và nút được thêm vào

Cách thêm Firebase vào ứng dụng của chúng tôi

Bây giờ, hãy cài đặt Firebase cho cơ sở dữ liệu. Chỉ cần cài đặt Firebase bằng lệnh bên dưới:

npm install firebase

Truy cập https://firebase.google.com/ và nhấp vào Đi tới bảng điều khiển ở góc trên bên phải.

Hình ảnh

Sau đó, nhấp vào Thêm dự án.

Hình ảnh

Sau khi tạo Project, hãy nhấp vào nút code để tạo ứng dụng web trong Firebase. Đặt tên cho ứng dụng và chúng ta đã sẵn sàng.

Hình ảnh

Bây giờ, chúng ta sẽ thêm tất cả dữ liệu cấu hình mà chúng ta phải lưu trữ trong ứng dụng React của mình. Vì vậy, hãy tạo một tệp có tên là firebaseConfig.js và thêm chúng.

Hình ảnh

Chúng ta sẽ cần cơ sở dữ liệu, vì vậy hãy khởi tạo nó. Ngoài ra, hãy xuất ứng dụng const và cơ sở dữ liệu như thế này:

import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore';

const firebaseConfig = {
//Your Firebase Data
};

export const app = initializeApp(firebaseConfig);
export const database = getFirestore(app)

Nhập ứng dụng và cơ sở dữ liệu vào tệp App.js. Và chuyển cơ sở dữ liệu dưới dạng props cho thành phần Docs. Chúng ta sẽ sử dụng nó sau để thêm dữ liệu vào Firebase Firestore.

import './App.css';
import Docs from './components/docs';
import { app, database } from './firebaseConfig';

function App() {
return (
<Docs database={database}/>
);
}

export default App;

Và trong thành phần Docs. Ngoài ra, hãy nhận dữ liệu xuất từ ​​cơ sở dữ liệu từ 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}
>
Add a Document
</button>

<Modal
open={open}
setOpen={setOpen}
/>
</div>
)
}

Bây giờ, chúng ta hãy cấu hình Cơ sở dữ liệu Firestore của mình.

Vào cơ sở dữ liệu Firestore từ thanh bên trái và nhấp vào Tạo cơ sở dữ liệu.

Hình ảnh

Chúng tôi sẽ khởi động Cơ sở dữ liệu của mình ở Chế độ Sản xuất. Vì vậy, hãy nhấp vào Tiếp theo, rồi Kích hoạt.

Hình ảnh

Chúng ta phải công khai các quy tắc bảo mật, chỉ trong thời điểm này. Vì vậy, hãy nhấp vào Quy tắc ở tab trên cùng và chỉnh sửa các quy tắc sau. Điều này có nghĩa là bất kỳ ai cũng có thể ghi dữ liệu hoặc đọc chúng, ngay cả khi không xác thực.

rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}

Cách thêm dữ liệu Docs vào cơ sở dữ liệu Firestore

Bây giờ, chúng ta hãy thực sự thêm dữ liệu của mình. Nhưng trước đó, chúng ta cần lấy dữ liệu từ trường nhập.

Vì vậy, trong thành phần Docs, hãy tạo một trạng thái sẽ lưu trữ dữ liệu này.

const [title, setTitle] = useState('')

Truyền tiêu đề và setTitle vào thành phần modal.

<Modal
open={open}
setOpen={setOpen}
title={title}
setTitle={setTitle}
/>

Nhận cả hai dưới dạng đạo cụ và đặt chúng vào trường nhập liệu.

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='Add the Title'
className='add-input'
onChange={(event) => setTitle(event.target.value)}
value={title}
/>
<div className='button-container'>
<button
className='add-docs'
>
Add
</button>
</div>
</Box>
</Modal>
</div>
);
}

Bây giờ, nếu chúng ta nhập gì đó vào ô nhập, nó sẽ được lưu trữ bên trong trạng thái tiêu đề .

Tiếp theo, chúng ta cần một hàm sẽ kích hoạt các hàm thêm dữ liệu, hãy tạo nó.

Trong Docs.js, tạo một hàm và truyền nó vào thành phần modal:

const addData = () => {

}

Nhận nó trong thành phần modal và chỉ cần liên kết nó với nút Thêm như thế này:

<div className='button-container'>
<button
className='add-docs'
onClick={addData}
>
Add
</button>
</div>

Bây giờ, hàm addData sẽ chạy khi chúng ta nhấp vào nút Add.

Bây giờ để gửi dữ liệu từ React đến Firebase một cách linh hoạt, hãy nhập một vài thứ từ Firebase Firestore:

import { addDoc, collection } from 'firebase/firestore';

Ở đây, chúng ta sẽ sử dụng collectionđể tạo bộ sưu tập dữ liệu trong Firebase và addDoc sẽ thêm dữ liệu vào bộ sưu tập đó.

Trước tiên, hãy tạo một tham chiếu bộ sưu tập. Nó sẽ lấy cơ sở dữ liệu mà chúng ta lấy được từ firebaseConfig.js và tên của bộ sưu tập mà chúng ta muốn sử dụng.

const collectionRef = collection(database, 'docsData')

Bây giờ, trong hàm addData, chúng ta hãy sử dụng addDoc . Hàm addDoc này sẽ lấy tham chiếu bộ sưu tập và chính dữ liệu.

const addData = () => {
addDoc(collectionRef, {
title: title
})
.then(() => {
alert('Data Added')
})
.catch(() => {
alert('Cannot add data')
})
}

Bây giờ, thêm một cái gì đó vào đầu vào văn bản và nhấp vào Thêm. Nó sẽ được thêm vào Firebase Firestore, với cảnh báo rằng dữ liệu đã được thêm vào. Nhưng nếu không thành công, chúng ta sẽ nhận được "Không thể thêm dữ liệu".

Hình ảnh

Nếu chúng ta làm mới cơ sở dữ liệu, chúng ta sẽ thấy mục mới này:

Hình ảnh

Và đó là cách chúng ta thêm dữ liệu. Chúng ta cũng đóng hộp thoại sau khi thêm dữ liệu.

Tạo một hàm handleClose và gọi hàm này ngay sau khối then trong hàm addData .

const addData = () => {
addDoc(collectionRef, {
title: title
})
.then(() => {
alert('Data Added');
handleClose()
})
.catch(() => {
alert('Cannot add data')
})
}

Cách đọc dữ liệu từ Firebase

Bây giờ, hãy đọc dữ liệu mà chúng ta đã thêm vào Firebase. Chúng ta sẽ cần hàm onSnapshot cho việc đó. Hàm onSnapshot lấy dữ liệu theo thời gian thực.

Đầu tiên, hãy nhập nó từ Firebase như thế này:

import { addDoc, collection, onSnapshot } from 'firebase/firestore';

Sau đó, tạo một hàm getData sẽ được kích hoạt khi trang của chúng ta tải. Vì vậy, chúng ta sẽ đưa onSnapshot này vào React useEffect Hook.

const getData = () => {
onSnapshot(collectionRef, (data) => {
console.log(data.docs.map((doc) => {
return {...doc.data(), id: doc.id}
}))
})
}

Sau đó, gọi hàm này bên trong useEffect Hook.

useEffect(() => {
getData()
}, [])

Hình ảnh

Nhưng như bạn thấy, chúng ta đang nhận dữ liệu hai lần. Đó là vì chúng ta đang sử dụng React phiên bản 18, bao gồm cả concurrent rendering . Đó là lý do tại sao useEffect hook sẽ chạy hai lần.

Để giải quyết vấn đề này, chúng ta cần tạo tham chiếu useRef .

const isMounted = useRef()

Sau đó trong useEffect Hook, chúng ta phải kiểm tra xem isMounted.current có đúng không. Vì vậy, nếu đúng, chúng ta sẽ không trả về gì cả. Và sau đó chúng ta sẽ đặt isMounted.current thành true, và sau đó chúng ta sẽ gọi hàm getData của mình.

useEffect(() => {
if(isMounted.current){
return
}

isMounted.current = true;
getData()
}, [])

Và nếu bây giờ chúng ta làm mới trang, chúng ta sẽ chỉ nhận được dữ liệu một lần.

Hình ảnh

Bây giờ, chúng ta phải đưa dữ liệu này vào trạng thái mảng. Vậy, hãy làm điều đó.

Tạo trạng thái của docsData .

 const [docsData, setDocsData] = useState([]);

Và đặt dữ liệu đến bên trong trạng thái này bằng cách sử dụng setDocsData .

const getData = () => {
onSnapshot(collectionRef, (data) => {
setDocsData(data.docs.map((doc) => {
return {...doc.data(), id: doc.id}
}))
})
}

Bây giờ, chúng ta hãy ánh xạ mảng để dữ liệu hiển thị trên UI.

<div>
{docsData.map((doc) => {
return (
<div>
<p>{doc.title}</p>
</div>
)
})}
</div>

Thao tác này sẽ hiển thị toàn bộ dữ liệu trong ứng dụng React của chúng ta.

Hình ảnh

Chúng ta sẽ thấy cả hai tài liệu trên trang của mình. Nhưng hãy làm cho chúng xuất hiện trong một lưới. Cung cấp cho các vùng chứa div classNames của grid-main và grid-child .

<div className='grid-main'>
{docsData.map((doc) => {
return (
<div className='grid-child'>
<p>{doc.title}</p>
</div>
)
})}
</div>

Và trong CSS, hãy thêm các lớp sau:

.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;
}

Bây giờ, ứng dụng của chúng ta sẽ trông như thế này:

Hình ảnh

Cách lấy ID và chuyển hướng đến trang Chỉnh sửa tài liệu

Bây giờ, mỗi mục trên đều có một ID. Chúng ta sẽ sử dụng các ID này để chuyển hướng đến một trang khác, nơi chúng ta có thể chỉnh sửa các mục và viết nội dung chính của mình.

Để làm được điều đó, chúng ta cần hai gói. Một là React-Router để chuyển hướng chúng ta, và một là React-Quill cho trình soạn thảo của chúng ta. Cài đặt chúng như thế này:

npm i react-quill react-router-dom@6

Bây giờ, hãy cấu hình định tuyến đến một trang khác. Nhưng trước tiên chúng ta cần một trang khác. Vậy, hãy tạo nó.

Tạo một thành phần có tên là EditDocs. Biến nó thành một thành phần chức năng.

import React from 'react'

export default function EditDocs() {
return (
<div>EditDocs</div>
)
}

Để cấu hình định tuyến, hãy đến index.js , điểm vào của ứng dụng. Gói BrowserRouter bên trong .

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();

Bây giờ, chúng ta có thể sử dụng định tuyến ở bất cứ đâu vì chúng ta đang khai báo BrowserRouter ở cấp độ cơ bản.

Bây giờ, hãy đến với tệp App.js. Nhập Routes và Route từ React-Router. Chúng tôi cũng thêm ID vào đường dẫn editDocs để có thể thấy ID trên thanh địa chỉ.

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;

Và thêm các tuyến đường sau. Nếu chúng ta vào '/editDocs/:id' , chúng ta sẽ thấy trang editDocs của mình.

Hình ảnh

Bây giờ, chúng ta cần lấy ID cụ thể từ các tài liệu và gửi đến trang editDocs.

Tạo hàm getID và gán hàm này cho các tài liệu.

const getID = () => {

}
<div className='grid-main'>
{docsData.map((doc) => {
return (
<div className='grid-child' onClick={() => getID(doc.id)}>
<p>{doc.title}</p>
</div>
)
})}
</div>

Bây giờ, nếu chúng ta nhấp vào tài liệu, chúng ta sẽ nhận được ID của tài liệu đó khi đăng nhập vào bảng điều khiển.

const getID = (id) => {
console.log(id)
}

Hình ảnh

Bây giờ, hãy gửi ID này đến trang editDocs bằng cách sử dụng useNavigate .

Đầu tiên, import useNavigate từ react-router.

import { useNavigate } from 'react-router-dom';

Sau đó, tạo một thể hiện của useNavigate như thế này:

let navigate = useNavigate();

Sau đó, để chuyển ID, chỉ cần làm như sau. Chúng tôi sẽ tự gửi mình đến trang editDocs, cùng với ID.

const getID = (id) => {
navigate(`/editDocs/${id}`)
}

Bây giờ, hãy nhận ID của chúng ta ở đầu kia. Trong thành phần editDocs, chúng ta cần sử dụng useParams từ react-router.

Vì vậy, hãy nhập nó và tạo một phiên bản:

import { useParams } from 'react-router-dom';

let params = useParams();

Ngoài ra, nếu chúng ta điều khiển nó, chúng ta sẽ thấy ID.

import { useParams } from 'react-router-dom';

let params = useParams();
console.log(params)

Hình ảnh

Chúng ta có thể thấy rằng chúng ta nhận được ID trong thanh địa chỉ cũng như trong bảng điều khiển.

Bây giờ, hãy thêm React Quill vào trang editDocs của chúng ta.

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>
)
}

Chúng ta phải import React-Quill và CSS.

Hình ảnh

Nhưng chúng ta có thể thấy chúng ta có hai thanh công cụ ở đây. Để giải quyết vấn đề này, chỉ cần xóa React.StrictMode khỏi 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();

Và chúng ta sẽ ổn thôi.

Hình ảnh

Bây giờ, chúng ta cần một trạng thái cho dữ liệu React Quill này. Vậy, hãy tạo nó. Ngoài ra, chúng ta sẽ tạo một hàm để kích hoạt khi chúng ta nhập.

const [docsDesc, setDocsDesc] = useState('');
const getQuillData = () => {

}

Bây giờ, hãy liên kết hàm và trạng thái với React Quill.

<ReactQuill
value={docsDesc}
onChange={getQuillData}
/>

Trong hàm getQuillData , hãy liên kết giá trị với trạng thái docsDesc bằng cách sử dụng hàm setDocsDesc .

const getQuillData = (value) => {
setDocsDesc(value)
}

Và chúng ta đã hoàn thành ở đây. Bạn có thể điều khiển trạng thái docsDesc này để kiểm tra.

Bây giờ chúng ta đã có ID và dữ liệu mà chúng ta có thể sử dụng để cập nhật tài liệu. Vậy, hãy thực hiện điều đó.

Cách cập nhật tài liệu

Chúng ta cần hai thứ, updateDoc và hàm collection . Chúng ta sẽ sử dụng hàm Debounce để gọi hàm updateDoc. Điều này có nghĩa là khi chúng ta nhập xong, sau 5 hoặc 10 giây, hàm updateDoc của chúng ta sẽ chạy.

Vậy, chúng ta hãy tạo một hàm:

const updateDocsData = () => {

}

Chúng ta cũng cần chỉ định bộ sưu tập. Đối với điều đó, chúng ta cần cơ sở dữ liệu từ App.js. Vì vậy, hãy lấy nó bằng cách sử dụng props.

<Route path="/editDocs/:id" element={<EditDocs database={database}/>} />

Bây giờ, chúng ta hãy tạo một tham chiếu bộ sưu tập.

const collectionRef = collection(database, 'docsData')

Bây giờ để chống dội ngược, chúng ta cần updateDocsData trong hook useEffect.

useEffect(() => {
const updateDocsData = () => {

}
}, [])

Bây giờ, hãy thêm hàm setTimeout với một khoảng thời gian. Điều này có nghĩa là hàm đó sẽ chạy sau khoảng thời gian được chỉ định đó. Đặt khoảng thời gian là 1000 mili giây hoặc 1 giây .

useEffect(() => {
const updateDocsData = setTimeout(() => {

}, 1000)
return () => clearTimeout(updateDocsData)
}, [])

Bây giờ, bên trong setTimeOut, hãy thêm hàm updateDoc. Vì vậy, bên trong biến document, chúng ta đang truyền collectionRef và ID từ params. Và sau đó, updateDoc lấy biến document đó làm tham số đầu tiên.

const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {

})
}, 1000)

Chúng ta cũng hãy nhập hàm doc . Hàm này chỉ định tài liệu nào sẽ được cập nhật bằng cách sử dụng ID làm khóa chính.

import {
updateDoc,
collection,
doc
} from 'firebase/firestore';

Bây giờ chúng ta hãy truyền dữ liệu vào tham số thứ hai trong hàm updateDoc.

useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [])

Trong mảng phụ thuộc, thêm trạng thái của docsDesc. Vì vậy, sau khi chúng ta nhập gì đó , hàm updateDoc sẽ chạy sau 1 giây.

useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
alert('Saved')
})
.catch(() => {
alert('Cannot Save')
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])

Vì vậy, hãy nhập gì đó vào trình soạn thảo và nó sẽ được lưu bên trong cơ sở dữ liệu.

Hình ảnh

Và dữ liệu ở đây:

Hình ảnh

Nếu chúng ta thêm gì đó nữa, chúng ta sẽ thêm vào dữ liệu trước đó:

Hình ảnh

Hình ảnh

Làm thế nào để lấy lại dữ liệu từ cơ sở dữ liệu đến trình soạn thảo

Bây giờ, nếu chúng ta quay lại và nhấp vào bất kỳ tài liệu nào, dữ liệu sẽ là null hoặc bị xóa. Vì vậy, chúng ta phải lấy dữ liệu từ cơ sở dữ liệu và đặt nó vào trình chỉnh sửa.

Chúng ta sẽ sử dụng hàm onSnapshot để thực hiện điều đó.

import {
updateDoc,
collection,
doc,
onSnapshot
} from 'firebase/firestore';
const getData = () => {

}

useEffect(() => {
if(isMounted.current){
return
}

isMounted.current = true;
getData()
}, [])

Vì vậy, nó giống như chúng ta đã làm trong thành phần Docs. Chúng ta cần chỉ định dữ liệu nào để lấy bằng tham số ID. Và sau đó chúng ta truyền tài liệu này đến hàm onSnapshot để lấy dữ liệu chúng ta cần.

const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
console.log(docs.data().docsDesc)
})
}

Hình ảnh

Hãy đặt docs.data().docsDesc này ở trạng thái docsDesc bằng cách sử dụng setDocsDesc. Vì vậy, nếu tài liệu tải, nó sẽ được đặt ở đó.

Thêm một số dữ liệu, sau đó quay lại. Và nếu bạn quay lại cùng một thành phần, mô tả tài liệu sẽ ở đó.

Hình ảnh

Bây giờ ở trang chủ nơi chúng ta thấy tất cả dữ liệu, chúng ta cũng cần thêm mô tả nếu có.

 <div dangerouslySetInnerHTML={{__html: doc.docsDesc}} />

Chúng tôi đang sử dụng dangerouslySetInnerHTML vì dữ liệu được thêm vào dưới dạng thẻ trong React Quill. Điều đó giúp việc hiển thị định dạng dễ dàng hơn.

Hình ảnh

Bạn thấy đấy, tôi đã thêm một số định dạng như chữ in đậm và chữ in nghiêng .

Bây giờ, chúng ta cần thực hiện một số sửa đổi nhỏ. Trong tệp App.js (nơi chúng ta thêm tiêu đề tài liệu), hãy thêm phần mô tả, ban đầu sẽ để trống.

const addData = () => {
addDoc(collectionRef, {
title: title,
docsDesc: ''
})
.then(() => {
alert('Data Added');
handleClose()
})
.catch(() => {
alert('Cannot add data')
})
}

Vì vậy, nếu chúng ta tạo một tài liệu, chúng ta sẽ có docsDesc trong Firestore Document. Điều đó sẽ ngăn ứng dụng của chúng ta bị sập khi chúng ta vào trang EditDocs.

Bây giờ, trong trang EditDocs, hãy thêm tiêu đề tài liệu để nó hiển thị ở trên cùng. Tạo một trạng thái có tên là documentTitle và thiết lập nó.

const [documentTitle, setDocumentTitle] = useState('')

const getData = () => {
const document = doc(collectionRef, params.id)
onSnapshot(document, (docs) => {
setDocumentTitle(docs.data().title)
setDocsDesc(docs.data().docsDesc);
})
}

Và hiển thị trạng thái này ở trên cùng:

<h1>{documentTitle}</h1>

Sau đây là toàn bộ mã cho trang EditDocs cho đến thời điểm hiện tại:

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('Saved')
})
.catch(() => {
alert('Cannot Save')
})
}, 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>
)
}

Làm thế nào để thêm một số phong cách

Bây giờ chúng ta hãy thêm một số kiểu dáng vào trang EditDocs này:

<div className='editDocs-main'>
<h1>{documentTitle}</h1>
<div className='editDocs-inner'>
<ReactQuill
className='react-quill'
value={docsDesc}
onChange={getQuillData}
/>
</div>
</div>

Và trong CSS, thêm kiểu sau:


.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;
}

Chúng tôi đang thêm bóng hộp, xóa đường viền React Quill và căn giữa mọi thứ.

Đây là giao diện trang chỉnh sửa tài liệu của chúng ta hiện tại:

Hình ảnh

Bây giờ đến phần cuối cùng: hãy thay thế cảnh báo của chúng ta bằng tin nhắn toast. Chúng ta cần thêm một gói nữa có tên là React Toastify . Vậy, hãy cài đặt nó.

npm i react-toastify

Sau đó chúng ta cần nhập hai thứ này:

import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

Và sau đó,thành phần.

Bây giờ, đối với thông báo toast, bạn chỉ cần làm như sau:

useEffect(() => {
const updateDocsData = setTimeout(() => {
const document = doc(collectionRef, params.id)
updateDoc(document, {
docsDesc: docsDesc
})
.then(() => {
toast.success('Document Saved', {
autoClose: 2000
})
})
.catch(() => {
toast.error('Cannot Save Document', {
autoClose: 2000
})
})
}, 1000)
return () => clearTimeout(updateDocsData)
}, [docsDesc])

Chúng ta có toast.success để cảnh báo thành công và toast.error để cảnh báo lỗi.

Hình ảnh

Phần kết luận

Và thế là bạn đã tạo được bản sao của Google Docs. Bạn có thể thoải mái thử nghiệm và cải thiện nó.

Bạn có thể lấy mã đầy đủ tại đây: https://github.com/nishant-666/Google-Docs-Clone

Ngoài ra, hãy xem kênh Cybernatico của tôi để biết thêm nhiều hướng dẫn tuyệt vời như thế này.

Chúc bạn học tập vui vẻ.

Source: https://www.freecodecamp.org/news/build-a-google-docs-clone-with-react-and-firebase/