Next 11장 - query, draft editor
포스트
취소

Next 11장 - query, draft editor

Next

useQuery 사용한 api요청

나를 너무나도 괴롭혔던 것들 메모하기

1
2
3
4
5
6
7
8
9
10
const router = useRouter();

const {id} = router.query;

function fetchPost(id){
    const response = await api.get(`/post/${id}`);
    return response.data;
};

const {isLoading, data, } = useQuery(["post", id], fetchPost(id));
  • 기억에 의존하기에 정확하지 않을 수 있지만 흐름을 이해해본다.
  • 본인이 하려던 것
    • 페이지 번호에 해당하는 쿼리를 찾아, api요청을 보내 게시글을 찾는다.
  • 문제
    • 렌더링 될 때 query를 파악하기도 전에, api요청이 먼저 가게 되어, undefined로 요청이 간다.
  • 해결
    • useEffect를 사용하여 router가 변할 때마다 id를 받아와 새롭게 요청을 한다.
    • router도 변하기 때문에 이를 감지할 수 있어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const [apiNumber, setApiNumber] = useState<number|undefined>();
const router = useRouter();


useEffect(() => {
    const id = Number(router.query.id);
    setApiNumber(id);
}, [id])

function fetchPost(apiNumber){
    const reponse = await api.get(`/post/${apiNumber}`);
    return response.data;
}

const {isLoading, data, } = useQuery(["post", apiNumber], fetchPost(apiNumber));

custom hook에서 data 꺼내 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
export interface PostType {
  postNumber: number;
  author: string;
  title: string;
  body: string;
  date: string;
  views: number;
  likes: number;
  comments: CommentType[];
}

export function usePostDetail(id: number) {
  const fetchPost = async (): Promise<PostType> => {
    const response = await api.get<PostType>(`/post/${id}`);
    return response.data;
  };
  return useQuery<PostType>(['post', id], fetchPost, {
    staleTime: 2000,
  });
}

export default function index() {
  const [postNum, setPostNum] = useState<number | undefined>();
  const router = useRouter();

  useEffect(() => {
    const id = Number(router.query.id);
    setPostNum(id);
  }, [router]);

  return (
    <Container>
      {postNum && <PostDetail id={postNum} />}
      <Login />
    </Container>
  );
}

export default function PostDetail({ id }: { id: number }) {
  const queryResult = usePostDetail(id);
//   const {data} = usePostDetail(id); => error

  console.log(queryResult);

  if (queryResult.isLoading) {
    return <div>Loading...</div>;
  }

  if (queryResult.isError) {
    const error = queryResult.error as Error;
    return <div>Error: {error.message}</div>;
  }

  if (!queryResult.data) {
    return <div>No data available</div>;
  }

  const data: PostType = queryResult.data;

  console.log(data);

  return <Container></Container>;
}


// data
{
    postNumber: 123,
  author: "choigirang",
  title: "안녕하세요",
  body: "<p>첫 글입니다.</p>",
  date: "2023-08-17",
  views: 0,
  likes: 0,
  comments: [];
}
  • 위의 게시글 찾기를 custom hook을 사용하여 페이지 번호를 파악하고 번호와 일치하는 게시글을 받아오도록 한다.
  • querydata를 바로 사용하려고 했을 때 문제가 발생했는데, 이는 useQuery 안에는 Loading, data, Error 등의 여러가지 메서드가 있기 때문에 이 중 data를 특정해서 추출해야 한다.

fetch 등의 함수

  • 컴포넌트 안에 fetch함수를 사용했지만, 이는 컴포넌트가 렌더링 될 때마다 함수도 새로이 사용되기 때문에 낭비다.
  • 이럴 경우 꼭 컴포넌트와 분리하여 사용하거나, custom Hook을 만들어 사용하자.

Draft.js

  • 에디터 작성을 위한 여러가지 라이브러리 중 Draft.js를 써보기로 했다.
  • quil, Toast Ui 등 여러가지 라이브러리가 있었지만, 라이브러리 install 횟수가 가장 많은 것을 택했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import React, { useState, useEffect } from "react";
import { ContentState, convertToRaw, EditorState } from "draft-js";
import { Editor } from "react-draft-wysiwyg";
import draftToHtml from "draftjs-to-html";

import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import styled from "styled-components";
import htmlToDraft from "html-to-draftjs";

interface IEditor {
  htmlStr: string;
  setHtmlStr: React.Dispatch<React.SetStateAction<string>>;
}

export default function PostEditor({ htmlStr, setHtmlStr }: IEditor) {
  const [editorState, setEditorState] =
    useState < EditorState > (() => EditorState.createEmpty());

  useEffect(() => {
    const blockFromHtml = htmlToDraft(htmlStr);
    if (blockFromHtml) {
      const { contentBlocks, entityMap } = blockFromHtml;
      const contentState = ContentState.createFromBlockArray(
        contentBlocks,
        entityMap
      );
      const editorState = EditorState.createWithContent(contentState);
      setEditorState(editorState);
    }
  }, []);

  const onEditorStateChange = (editorState: EditorState) => {
    setEditorState(editorState);
    setHtmlStr(draftToHtml(convertToRaw(editorState.getCurrentContent())));
  };

  const toolbar = {
    list: { inDropdown: true },
    textAlign: { inDropdown: true },
    link: { inDropdown: true },
    history: { inDropdown: true },
    image: { uploadCallback: uploadCallback },
  };

  const localization = {
    locale: "ko",
  };

  return (
    <>
      <Container>
        <Editor
          editorClassName="editor"
          toolbarClassName="toolbar"
          toolbar={toolbar}
          placeholder="내용을 입력하세요"
          localization={localization}
          editorState={editorState}
          onEditorStateChange={onEditorStateChange}
        />
      </Container>
    </>
  );
}
  • 상당히 복잡하고 아직 다 이해하진 못 했지만 대략적으로, draft.js를 원활히 사용하기 위해 기본적으로 설치해줘야 하는 것들이 많다.
    • draft.js
    • draftjs-to-html
    • html-to-draftjs
    • react-draft-wysiwyg
  • 위 항목들을 다 설치해야 되고 typeScript를 사용할 경우, 또 타입마다 설치해줘야 한다.
  • 코드의 대략적인 설명은, 에디터에 입력되는, 혹은 에디터를 사용하여 부여되는 효과 별로 markdown으로 변환된다.
  • 변환된 문자열을 보내고 저장해두었다가, 불러올 때는 다시 마크다운을 문자열로 변환하여 사용한다.
  • 여러가지 옵션이 있기 때문에 커스텀이 자유롭고, 본인에게 필요한 기능과 옵션을 십분 활용하기로 하자. [https://jforj.tistory.com/213]
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.