logo

Blog

React Quill에서 이미지를 업로드하기 위한 올바른 방법

#react#react-quill
ryxxn profileryxxn
2024.04.30
thumbnail

1. 대부분의 React Quill 사용법

사내에서 상품 판매 form을 만들어야 해서 React Quill 에디터를 사용한 적이 있다.

기본적으로 React Quill 에디터에서 이미지를 업로드하면 Base64로 업로드되기 때문에 서버에 이미지를 업로드하고 url을 받는 방법을 찾기 위해 구글링을 해보았다.

거짓말 안 보태고 모든 아티클에서 imageHandler를 이렇게 사용하라고 알려주고 있었다.

javascript
export default function Editor({ value, onChange, ...other }) {  const quillRef = useRef(null);
  const imageHandler = () => {    const input = document.createElement('input');    input.setAttribute('type', 'file');    input.setAttribute('accept', 'image/*');    input.click();
    input.addEventListener('change', () => {      const files = input.files;      // image upload logic    });  };
  const modules = useMemo(() => {    return {      toolbar: {        container: [["link", "image"]],        handlers: {          image: imageHandler,        },      },      history: {},      clipboard: {},      // custom    };  }, []);
  return (      <ReactQuill        ref={quillRef}        theme="snow"        modules={modules}        formats={formats}        value={value}        onChange={onChange}        placeholder="input text..."        {...other}      />  );}

어차피 DOM에 첨부를 위한 임시 input을 쓰는 거니깐 큰 문제는 없을 거라 생각해서

나도 이런 방식으로 에디터 컴포넌트를 만들었다.


2. 버그 발생

이미지를 aws s3에 presigned url 방식으로 관리하고 있었는데

bucket key에 상품명과 상품 코드를 넣어달라는 요청이 있었다.

그러기 위해서는 상품명, 코드가 없으면 이미지 업로드가 불가능한 상태..

그래서

typescript
const uploadFilesAndGetUrls = (files) => {    if (!productName || !productCode) {      alertToast("상품명과 상품코드를 입력해야 사진 첨부가 가능합니다.");      return null;    }
    // ... s3 이미지 업로드 로직

이런식으로 했고, 상품명과 같은 입력 데이터는 react-hook-form으로 관리했다.

그래서 Editor 컴포넌트에

tsx
// ...
export default function Editor({  value,  onChange,  uploadFilesAndGetUrls,  ...other}) {  const quillRef = React.useRef(null);
  const imageHandler = () => {    const input = document.createElement("input");    input.type = "file";    input.multiple = true;    input.accept = "image/*";    input.click();
    input.addEventListener("change", () => {      const files = input.files;
      const imgUrls = uploadFilesAndGetUrls(files);      if (!imgUrls) return;
      imgUrls.forEach((file) => insertImage(file, quillRef));    });  };
  // ...

이렇게 uploadFilesAndGetUrls 함수를 prop으로 넣어주었고 테스트를 해보았다.

그런데 자꾸만 이 부분에서 막혔다.

javascript
alertToast("상품명과 상품코드를 입력해야 사진 첨부가 가능합니다.");
  • 처음 상품을 등록할 때 form을 다 입력해도 막힘
  • 새로고침 후 이미지를 업로드하면 또 안 막힘

이미지 처리 로직 자체도 상당히 길었어서 여기서 한참을 헤매게 됩니다.


3. 해결

여러 테스트를 해봤는데 첫 로드 이후 입력하는 값들이 함수에 반영이 안 되고 있었음.

그 말은 동적으로 바뀌는 값들을 반영하지 못 한다는 건데..

imageHandler 함수를 들여다보면

javascript
  const imageHandler = () => {    const input = document.createElement("input");    input.type = "file";    input.multiple = true;    input.accept = "image/*";    input.click();
    input.addEventListener("change", () => {      const files = input.files;
      const imgUrls = uploadFilesAndGetUrls(files);      if (!imgUrls) return;
      imgUrls.forEach((file) => insertImage(file, quillRef));    });  };

ReactDom이 아니라 DOM에 생성한 input에 uploadFilesAndGetUrls 함수를 넣으니깐

state가 변경돼도 반영이 안 되는 것이었음.

구글링으로 참고한 포스트들을 원망하며 ReactDom 내에서 이미지 업로드를 해보기로 합니다.

jsx
// ...
export default function Editor({  value,  onChange,  uploadFilesAndGetUrls,  ...other}) {  const quillRef = React.useRef(null);
  // 이미지를 업로드하기 위한 input ref  const inputRef = React.useRef(null);
  const onImageUpload = () => {    const files = inputRef.current.files;    const imgUrls = uploadFilesAndGetUrls(files);    if (!imgUrls) return;
    imgUrls.forEach((file) => insertImage(file, quillRef));  };
  const modules = React.useMemo(() => {    return {      toolbar: {        container: [["link", "image"]],        handlers: {          // input trigger          image: () => inputRef.current.click(),        },      },    };  }, []);
  return (    <>      <ReactQuill        {/* ... */}      />
      {/* 이미지를 업로드하기 위한 input */}      <input        ref={inputRef}        type="file"        accept="image/*"        className="hidden"        onChange={onImageUpload}        multiple      />    </>  );}

해결했습니다.


4. 회고

ReactDom에서 DOM API를 사용했을 때 일어나는 버그를 몸소 느껴봤네요

정말 많은 사람들이 React Quill을 이렇게 쓰고 있는데 하필 나에게 이런 일이

그래서 React Quill 깃헙 이슈에 올려 공유했습니다.

https://github.com/zenoamaro/react-quill/issues/961

Related Articles