-
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에 대해 다시 한 번 찾아보았습니다.
참고 블로그를 꼼꼼히 읽던 중, 지금껏 제가 잘못 판단하고 있던 부분을 발견했습니다.
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