27일차) [React/JS] 웹 에디터(React-quill)

김재범·2022년 8월 11일
0

코드캠프

목록 보기
35/46
post-thumbnail

✍웹에디터(React-quill)

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는 서버사이드 렌더링을 지원하는데,
프리렌더링 단계에서 windowdocument가 존재하지 않기 때문

💡이를 해결해주는 것이 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여기 안에있는 태그들은 태그 자체로 인식하게끔 해줘 */}
      {/* 꺽쇠가 &lt, &gt로 나오게 됨(해킹방지) */}
      {/* 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 를 사용한다면
반드시 빈 태그 형식으로 작성

profile
지식을 쌓고 있습니다.

1개의 댓글

comment-user-thumbnail
2023년 11월 16일

앗 제가 겪고있는 상황과 너무 딱 맞아떨어져요ㅜㅜ 그런데 setValue is not a function 에러가 뜨는데 이는 어떻게 해결하셨나요?

답글 달기