-
Custom Hook에 ref를 전달하기 (Feat. React-Quill)Studying/React 2023. 7. 14. 17:03
문제 발생
React-Quill에서 이미지를 삽입할 때 사용할 이미지 핸들러를 만들고 사용하려는 과정에서 발생한 문제입니다.
이미지 핸들러에 대한 커스텀 훅을 만들었고, 이미지 핸들러는 ref를 통해 <ReactQuill> DOM에 접근해서 현재 마우스 커서 위치를 파악해야 하는데, 커스텀 훅에 ref 값을 전달할 방법이 없었습니다.
<ReactQuill>에 다음과 같이 ref를 등록했습니다.
const quillRef = useRef<ReactQuill>(); return ( <ReactQuill ref={(element) => { if (element !== null) { quillRef.current = element; } }} /> )
useImageHandler 훅에게 초기값을 전달하고 반환 값을 받습니다.
const [imageUrlHandler, imageHandler] = useImageHandler({ quillEditor: quillRef.current?.getEditor(), })
- 전달 값: ref 값에 접근해서 getEditor()를 통해 에디터 객체만 뽑아 전달한다.
- 반환 값: 이미지를 핸들링하는 imageUrlHandler, imageHandler 함수를 반환한다.
문제는 바로 이 부분이었습니다.
커스텀 훅에 초기값이 전달되는 시점에서 ref 값은 undefined인 것입니다.
ReactQuill이 DOM으로 형성되기 전에 훅에 초기값이 전달되기 때문입니다.
그래서 React Hook-flow에 대해 다시 한 번 찾아보았습니다.
참고 블로그를 꼼꼼히 읽던 중, 지금껏 제가 잘못 판단하고 있던 부분을 발견했습니다.
https://sambalim.tistory.com/153 useState의 초기값을 가지고 DOM 업데이트 라는 부분이었습니다.
커스텀 훅은 왜인지 useEffect 훅이 DOM 업데이트 이후에 실행되는 것처럼 DOM 업데이트 후에 실행된다고 생각했습니다. 초기값이 등록되는 것까지 DOM 업데이트 이후라고 착각한 것 같습니다.
괜히 콘솔에 훅 플로우를 찍어보기까지 했습니다. QuillEditor.tsx 파일이 실행되고 발생하는 훅 플로우 입니다.
콘솔에 출력한 훅 플로우 QuillEditor의 컴포넌트가 렌더링 되기 전에 커스텀 훅이 실행되는 게 보입니다.
그렇다면 커스텀 훅에 ref 값을 전달하는 방법은 아예 없는 걸까요?
해결 방법
매우 단순한 해결 방법이 있었습니다.
imageHandler 함수 props로 ref를 전달하면 됩니다.
왜 커스텀 훅의 초기값으로 ref를 전달한 뒤, imageHandler 함수에서 사용하는 방법만 고집한 건지 모르겠습니다.
다음 코드가 해결책을 반영한 imageHandler, imageUrlHandler 입니다.
const imageUrlHandler = useCallback((editor: any) => { const range = editor.getSelection(); const url = prompt(""); if (url) { editor.insertEmbed(range.index, "image", url); } }, []) const imageHandler = useCallback((editor: any) => { const input = document.createElement("input"); input.setAttribute("type", "file"); input.setAttribute("accept", "image/*"); input.setAttribute("name", "file"); input.click(); input.onchange = async (event: any) => { const file: File = event?.target?.files[0]; const formData = new FormData(); formData.append("file", file); uploadImage(formData) .then((res) => { const range = editor.getSelection(); editor.insertEmbed(range.index, "image", res.imageUrl); editor.setSelection(range.index + 1); }) } }, [])
과 같이 editor를 인자로 받아 사용함으로써 imageHandler를 훅으로 분리하되, ref.current.getEditor()도 접근할 수 있게 되었습니다.
any 타입을 쓴 이유는...
editor의 경우 Quill 타입이라는데, 빠른수정을 통해 import가 안 되어서 명시하지 못 했습니다.
event의 경우 React의 ChangeEvent라고 명시했더니 event.target.files[0]가 null일 수 있다고 하여 보류했습니다.
Quill Editor의 모듈도 다음과 같이 정의합니다.
const modules = useMemo( () => ({ toolbar: { container: [ [{ 'header': [1, 2, 3, 4, 5, 6, false] }], [{ 'font': [] }], ["bold", "italic", "underline", "strike", "blockquote"], [{ color: [] }, { 'background': [] }], [ { list: "ordered" }, { list: "bullet" }, { indent: "-1" }, { indent: "+1" }, ], ['link'], ['image', 'video'], ['clean'] ], handlers: { imageUrl: () => imageUrlHandler(quillRef.current?.getEditor()), image: () => imageHandler(quillRef.current?.getEditor()), }, }, }), []);
handlers에 imageUrl, image를 훅을 통해 받은 핸들러 함수로 지정해줍니다.
prop을 전달하지 않을 경우 imageUrl: imageUrlHandler 와 같이 단순하게 적을 수 있겠지만, 하는 수 없이 저만큼 길게 적혔습니다.
마음에 들지 않는 부분이라면 quillRef.current?.getEditor() 같이 긴 코드가 두 번이나 반복되어 적힌 것입니다. 그런데 editor 변수를 정의하여 바깥에 빼둘 경우 quillRef가 undefined일 때 정의되기 때문에 방법이 없었습니다.
editor를 상태로 관리하여 useEffect가 발생할 때 editor를 갱신하고,(그 시점에는 DOM이 생성되었으니까)
handlers에 imageHandler(editor) 와 같이 정의했는데,
handlers가 정의되는 순간 editor는 마찬가지로 초기값 undefined이기 때문에 별 소용이 없었습니다.
하여튼 훅으로 분리하는 것에 성공해서 다행이라는 생각이 듭니다.
+ 그런데 이 당시 왜 훅으로 분리한 건지 잘 기억이 안 나 모르겠고, 그냥 유틸 함수로 분리하는 게 더 목적에 부합하지 않나 생각이 듭니다.
'Studying > React' 카테고리의 다른 글
[React-Quill] inline style 적용하기 (0) 2023.07.22 뒤로가기/새로고침 시 '정말 나가시겠습니까?' 모달 만들기 (0) 2023.07.18 [react-quill] 커스텀 이미지 핸들러 구현하기 (0) 2023.07.14 Warning: React does not recognize the ` ` prop on a DOM element (0) 2023.07.12 React quill Editor + react-hook-form feat.TypeScript (0) 2023.07.11