Next13 블로그 프로젝트
React-Quill로 게시물 편집, DB에 저장하기
sakuraop
2023. 9. 18. 01:25
목차
1. router 만들기
2. 라우터에 들어갈 컴포넌트기 만들기
3. quill 만들기
4. server에 quill에 작성한 데이터 전송 (request)
5. DB에 저장
6. 저장 결과
7. react-quiil이 content를 저장하는 원리
1. router를 만들어줍니다.
- /manage/newpost 경로에 라우터를 만들어줍니다.
import Editor from "@/containers/Editor/Editor";
const NewPostRouter = async () => {
return (
<div>
<Editor />
</div>
);
};
export default NewPostRouter;
- Router는 라우팅 역할만 맡도록 합니다.
2. /src/containers/Editor/Editor.tsx 컴포넌트를 만들어 줍니다.
const Editor = () => {
const [contents, setContents] = useState("");
const quillProps = {
contents,
setContents,
};
return (
<div className={styles.container}>
<div className={styles.quillContainer}>
<Quill {...quillProps} />
</div>
</div>
);
};
export default Editor;
- 라우터에 전달할 에디터를 컴포넌트로 만듭니다.
3. quill을 Editor 안에 만들어줍니다.
https://www.npmjs.com/package/react-quill#quick-start
- react-quill npm 공식 문서
가장 기본적인 형태는 다음과 같습니다.
function MyComponent() {
const [value, setValue] = useState('');
return <ReactQuill theme="snow" value={value} onChange={setValue} />;
}
https://quilljs.com/docs/modules/toolbar/
- react-quill toolbar 커스텀 방법
이 커스텀 옵션 사진과 아래의 코드로 커스텀 방법을 쉽게 설명해보겠습니다.
const modules = {
toolbar: {
container: [
["underline", "strike", "blockquote"], // 글자 효과
[{ size: ["small", false, "large", "huge"] }], // 글자 크기
[{ color: [] }, { background: [] }], // 글자 색상, 글자 배경
["image", "video"],
],
},
};
- 컨테이너의 역할은 기능을 구분하는 경계입니다.
[ [기능1], [기능2], [기능3], [기능4] ]
이렇게 구분된 형태로 이해할 수 있습니다.
[{ color: [] }, { background: [] }], // 글자 색상, 글자 배경
- 작은 배열 안에 들어 있는 {color: []}, {background: []} 와 같은 것들이 속성을 결정합니다.
이렇게 만들어 준 modules 객체를 <ReactQuill modules={moduels} /> 와 같이 설정해주면 적용이 됩니다.
제가 만든 코드 예시)
"use client";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import styles from "./Quill.module.scss";
interface quillProps {
contents: string;
setContents: React.Dispatch<React.SetStateAction<string>>;
}
const Quill = ({ contents, setContents }: quillProps) => {
const modules = {
toolbar: {
container: [
["underline", "strike", "blockquote"], // 글자 효과
[{ size: ["small", false, "large", "huge"] }], // 글자 크기
[{ color: [] }, { background: [] }], // 글자 색상, 글자 배경
["image", "video"],
],
},
};
return (
<ReactQuill
className={styles.quill} // 없어도 됩니다.
value={contents} // value에는 input에 들어갈 state를 넣어줍시다.
onChange={setContents} // onChage에는 input을 바꿔줄 setState를 넣어줍시다.
modules={modules}
theme="snow" // 테마도 커스텀이 가능합니다. snow가 기본값입니다. 없어도 됩니다.
placeholder="내용을 입력해주세요." // 없어도 됩니다.
/>
);
};
export default Quill;
4. server에 데이터를 보내봅시다. (request)
- 이렇게 만들어 볼겁니다.
예시 코드) (placeholder와 import는 알아서 하시면 됩니다.)
"use client";
import React, { useState } from "react";
import Quill from "./Quill/Quill";
import styles from "./Editor.module.scss";
import axios from "axios";
import { useRouter } from "next/navigation";
const Editor = () => {
// 작성 완료되면 게시물로 redirect 할겁니다.
const router = useRouter();
const [title, setTitle] = useState("");
const [subtitles, setSubtitles] = useState("");
const [languages, setLanguages] = useState("");
const [contents, setContents] = useState("");
// quill에 전달할 state props
const quillProps = {
contents,
setContents,
};
// 작성하기 버튼 클릭하면,
const handleClickWriteButton = async (e: any) => {
e.preventDefault();
// api 요청을 보내고
const result = await axios.post("/api/manage/newpost", {title, subtitles, languages, contents});
// 게시물 작성이 완료되면 해당 게시물 주소로 redirect 합니다.
router.push(`/post/${result.data.id}`);
};
return (
<div>
<div>
{/* 제목 부제목 언어 input 굳이 없어도 됩니다. */}
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)}/>
<input type="text" value={subtitles} onChange={(e) => setSubtitles(e.target.value)}/>
<input type="text" placeholder="languages" value={languages} onChange={(e) => setLanguages(e.target.value)}/>
</div>
<div>
{/* state와 setState 전달합니다. */}
<Quill {...quillProps} />
</div>
<div>
<button onClick={(e) => handleClickWriteButton(e)}>작성하기</button>
<button>취소하기</button>
</div>
</div>
);
};
export default Editor;
5. DB에 저장해봅시다.
- 아래와 같이 api요청 경로로 '/api/manage/newpost' 폴더와 파일을 생성합니다.
수행할 작업은 다음과 같습니다.
- POST 요청인지 확인
- DB연결
- 게시물 작성자 정보 확인
- 게시물 번호 추가 (없어도 됨)
- DB에 게시물 정보 저장
- 게시물 번호를 포함하여 200 응답 => redirect할 주소로 이용됩니다.
기타 자잘한 설명은 주석에 써 놓았습니다.
코드 예시)
import { connectDB } from "@/utils/db/db";
import { getToken } from "next-auth/jwt";
const handler = async (req: any, res: any) => {
if (req.method == "POST") {
// DB와 Collection 연결
const db = (await connectDB).db("blog");
const postCollection = await db.collection("posts");
const countersCollection = await db.collection("counters");
// 게시물 작성자 정보
const token = await getToken({ req });
// 게시물 번호 정보 업데이트하기
const currentCounter = await countersCollection.findOne({ model: "posts" });
await countersCollection.findOneAndUpdate(
{ model: "posts" },
{ $inc: { count: 1 } },
{ upsert: true }
);
const { title, subtitles, languages, contents, src } = req.body; // 게시물 내용
const id = currentCounter ? currentCounter.count + 1 : 1; // 게시물 번호
const email = token?.email; // 작성자 email
const author = token?.name; // 작성자 닉네임
const date = new Date(); // 게시물 작성 시점
const commentCount = 0; // 댓글
const likes = 0; // 좋아요
const saveData = {
id,
title,
subtitles: subtitles.split(" "),
languages: languages.split(" "),
contents,
email,
src: src || "https://cdn.pixabay.com/photo/2023/09/03/11/13/mountains-8230502_1280.jpg",
author,
date,
commentCount,
likes,
};
const result = await postCollection.insertOne({ ...saveData }); // DB에 저장한 결과
return res.status(200).json({ id }); // 응답에 게시물 id를 포함하여 redirect할 수 있도록 합니다.
}
};
export default handler;
6. 결과
게시물 목록
게시물 상세 페이지
7. react-quiil이 content를 저장하는 원리
const Quill = ({ contents, setContents }: quillProps) => {
useEffect(() => {
console.log(contents);
}, [contents]);
작성하면서 출력을 해보면 됩니다.
작성된 글은 태그로 감싸지고,
그 안에 드래그된 텍스트나 커서 위치를 포함하는 태그에 style이 추가되는 방식입니다.