textarea의 단점을 보완해서 나온 라이브러리
📖 docs: https://www.npmjs.com/package/react-quill
☑️사용방법
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
export default function WebEditorPage() {
const handleChange = (value: string) => {
console.log(value);
};
return (
<div>
작성자: <input type="text" />
<br />
비밀번호: <input type="password" />
<br />
제목: <input type="text" />
<br />
내용: <ReactQuill onChange={handleChange} />
<br />
<button>등록하기</button>
</div>
);
}
❗ 이렇게사용하면 에러가뜸
Next.js는 서버사이드 렌더링을 지원하는데,
프리렌더링 단계에서 window
나 document
가 존재하지 않기 때문
💡
이를 해결해주는 것이 dynamic이다
해당 모듈을 런타임 시점에서 호출
import dynamic from 'next/dynamic';
const ReactQuill = dynamic( () => import('react-quill'), {
ssr : false
})
// reactquill을 다이나믹 임폴트를 할꺼야, 언제? 서버사이드렌더링이 false 일때
// 다이나믹 함수안에 넣어야함
를 추가.
이를 useForm에서 사용할 시에는
// import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import dynamic from "next/dynamic";
import { useForm } from "react-hook-form";
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
// reactquill을 다이나믹 임폴트를 할꺼야, 언제? 서버사이드렌더링이 false 일때
// 다이나믹 함수안에 넣어야함
export default function WebEditorPage() {
const { register, handleSubmit, setValue, trigger } = useForm({
mode: "onChange", // onChange가 일어나면 알려줘
});
// setValue : 자동으로 안되면 수동으로라도 넣어줘(useForm기능)
// trigger : 입력이 되면 입력이 되었다고 알려줘(useForm 기능)
const onChangeContents = (value: string) => {
setValue("contents", value === "<p><br></p>" ? "" : value);
// register로 등록하지 않고 contents라는 state에 value를 강제로 넣는 기능
// value === "<p><br></p>" ? "" : value)는 값이 입력되었다가 지워졌을 때 남는 태그를 지워줌
trigger("contents"); // onChange 되었다고 react-hook-form에 강제로 알려주는 기능
};
return (
<div>
작성자: <input type="text" {...register("writer")} />
{/* ...register에는 writer state, onChange등 다양한 기능이 있다. */}
<br />
비밀번호: <input type="password" {...register("password")} />
<br />
제목: <input type="text" {...register("title")} />
<br />
내용: <ReactQuill onChange={onChangeContents} theme="snow" />
{/* 여기있는 onChange는 reactquill 개발자가 만든거라 다름, onChange에 event가 아니라 value가 들어옴 */}
{/* reactquill에는 이미 onChange가 있어서 ...register가 안됨 */}
<br />
<button>등록하기</button>
</div>
);
}
이미 ReactQuill에는 onChange 요소가 들어가 있어 register가 적용이 안된다.
💡그렇기 때문에 setValue
라는 요소를 이용해 reack-hook-form
에 강제로 넣는다.
createBoard를 fetch하는 페이지
import { useQuery, gql } from "@apollo/client";
import { useRouter } from "next/router";
import Dompurify from "dompurify";
const FETCH_BOARD = gql`
query fetchBoard($boardId: ID!) {
fetchBoard(boardId: $boardId) {
_id
writer
title
contents
}
}
`;
export default function StaticRoutedPage() {
const router = useRouter();
const { data } = useQuery(FETCH_BOARD, {
variables: { boardId: router.query.id },
});
console.log(data);
return (
<>
<div>작성자: {data ? data.fetchBoard.writer : "받아오는 중 입니다"}</div>
<div>제목: {data && data.fetchBoard.title}</div>
{typeof window !== "undefined" && (
<div
dangerouslySetInnerHTML={{
__html: Dompurify.sanitize(data?.fetchBoard.contents),
}}
></div>
)}
{/* dangerouslySetInnerHTML여기 안에있는 태그들은 태그 자체로 인식하게끔 해줘 */}
{/* 꺽쇠가 <, >로 나오게 됨(해킹방지) */}
{/* Dompurify에 있는 sanitize를 이용하면 좀 더 안전하게됨 (데이터를 묶으면 됨) */}
{/* 서버에서 프리렌더링 될 때 Dompurify도 못찾음, typeofWindow 사용 */}
</>
);
}
// playground XSS 공격
// <img src='#' onerror='console.log(localStorage.getItem(\"accessToken\"))' />
dangerouslySetInnerHTML은 HTML문법을 사용하겠다는 뜻.
<div dangerouslySetInnerHTML={{ __html: Dompurify.sanitize(data?.fetchBoard.contents), }} </div>
여기안에 있는 태그는 태그로 인식하게 만들어달라.
div 및 span 태그
로 dangerouslySetInnerHTML 를 사용한다면
반드시 빈 태그 형식으로 작성
앗 제가 겪고있는 상황과 너무 딱 맞아떨어져요ㅜㅜ 그런데 setValue is not a function 에러가 뜨는데 이는 어떻게 해결하셨나요?