프로젝트/[리스렌즈] 가전 제품 렌탈 리뷰 커뮤니티(LeaseLens)

[리스렌즈] 리뷰 글 작성 페이지 (React-Quill, quill-image-resize, axios) 구현

hyunh404 2024. 7. 9. 10:03
728x90

 

리뷰를 작성할 수 있도록 글 쓰기 페이지를 구현했는데,

리뷰 이미지를 넣고, 글을 강조할 수 있는 꾸밈 요소를 구현하기 위해 React-Quill 라이브러리를 활용하기로 결정했다.

 

글을 작성할 수 있는 라이브러리 중 가장 많이 활용되고 있는 라이브러리이기 때문에 React-Quill을 활용하기로 결정했고,

React-Quill 의 꾸밈요소는 필요한 요소들로만 편집이 가능하기 때문에 편리하다는 것이 장점이다.

 


 

 

먼저 리스렌즈 리뷰 작성에는 이미지 업로드와, 글씨 크기, bold, underline 조정이 필요했기 때문에 toolbar를 편집해 필요한 요소들만 넣어주었다.

 

const modules = {
    toolbar: {
      container: [
        ["image"],
        [{ header: [1, 2, 3, 4, 5, false] }],
        ["bold", "underline"],
      ],
    },
  };

 

 

 

  • ["image"]: 이미지 삽입 도구. 이 버튼을 클릭하면 사용자가 이미지를 업로드하거나 삽입 가능.
  • [ { header: [1, 2, 3, 4, 5, false] } ]: header 옵션을 사용하여 텍스트의 머리글 크기를 설정하는 도구. 이 배열 내의 숫자는 헤더의 크기를 나타내며, 1은 가장 큰 헤더, 5는 가장 작은 헤더를 의미 -> false는 헤더가 아닌 일반 텍스트를 의미. 이 버튼을 클릭하면 사용자가 텍스트의 머리글 크기 선택 가능.
  • ["bold", "underline"]: 텍스트에 굵게(bold) 또는 밑줄(underline)을 적용할 수 있는 도구.

 

이로써 기본적인 React-Quill 라이브러리를 이용해 글을 작성할 수 있는 영역을 만들어 주었다.

 

React-Quill 글 쓰기 영역

 

 


 

라이브러리를 통해 글 쓰기 영역을 구현해준 후 리뷰 작성에 필수적인 제품 카테고리 선택, 제품명 선택, 별점을 선택할 수 있는 select box를 만들어 주었다.

 

또한 리뷰 제목을 작성할 수 있는 영역도 만들었으며,

신뢰도 있는 리뷰 인증 커뮤니티를 위한 가전 구독을 직접 사용해봤다는 인증 영수증을 함께 올리도록 하기 위해 인증 이미지를 업로드할 수 있는 영역도 만들어주었다.

 

카테고리, 제품명, 별점, 제목

 

728x90

 

 

이제 사용자가 선택하고, 입력한 데이터들을 서버로 보내기 위해 상태관리와 데이터 연결을 해줘야한다.

 

 

먼저,

카테고리를 선택하고 제품명을 불러오기 위해선 DB에 저장된 데이터가 필요하다.

 

따라서, GET 요청을 통해 제품 목록을 불러오겠다.

불러온 제품 목록은 useState로 배열로서 상태를 관리해주며, 카테고리가 선택에 따라 axios 요청이 실행되도록 useEffect를 설정해주었다.

 

useEffect(() => {
    axios
      .get(`${BACKHOST}/products?category=${categorySelect}`)
      .then((response) => {
        setProducts(response.data.data.products);
      })
      .catch((error) => {
        console.error("Error fetching products:", error);
      });
  }, [categorySelect]);

 

 

 

다음으로 사용자가 작성한 리뷰 데이터들을 서버에 보내기 위핸 POST 요청을 작성해주었다.

사용자가 입력한 모든 데이터를 formData로 묶어서 서버로 전송한다.

 

formData를 보내기 위해 header에 Content-Type을 같이 작성해서 보내주었다.

 

또한 필수 필드가 채워지지 않은 채로 작성 버튼을 누르게 되면 필드를 채워달라는 메세지가 뜨도록 해주었다.

 

axios
      .post(`${BACKHOST}/reviews`, formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      })
      .then((response) => {
        alert(response.data.message)
        navigate('/reviewlist');
      })
      .catch((error) => {
        console.error('Error submitting review:', error);
        alert("리뷰 인증 이미지를 함께 올려주세요!")
      });

 

 


 

 

다음으로 가장 고생을 많이 했던 리뷰 이미지를 삽입하고, 데이터를 서버로 전송하는 부분에 대해 작성해보려한다.

 

처음에는 라이브러리를 활용하기 위한 기본적인 이미지 핸들러 함수만 작성해주었었다.

삽입된 이미지 url을 formData에 저장하고 서버로 전송하는 함수였다.

 

 

여기서부터 문제가 발생했다.

 

React-Quill 라이브러리에서 이미지를 기본적으로 base64로 인코딩해 img 태그의 src 속성에 추가되면서 이미지 url이 매우 길어진다는 문제가 생겼고,
이로 인해 이미지가 DB에 저장되지 않는 문제가 발생했었다.

또한,
입력 값 변경 시 라이브러리가 사라지는 문제도 발생했었다.

 

 

입력값이 변경될때마다 라이브러리가 사라지는 문제는 useMemo 훅을 활용해 문제를 해결할 수 있었다.

 

React-Quill modules 객체가 컴포넌트 리렌더링 시마다 불필요하게 재생성되어 라이브러리가 사라지는 것 같다고 문제점을 파악했고,

재생성을 방지하기 위해 useMemo로 modules 객체를 관리함으로써 더이상 값 변경 시 라이브러리가 사라지지 않는 것을 확인해 문제를 해결할 수 있었다.

 

 

따라서 이미지 url을 짧은 길이로 수정해 서버로 보내야하는 수정 사항이 생겼고,

백엔드 분들께 문제 상황을 설명 후 서버에 먼저 이미지를 업로드하고 서버에 있는 이미지 url을 받아와 화면에 표시하고 서버에 전송하는 것으로 문제를 해결하기로 결정했다.

 

 

이를 구현하기 위해 Quill 에디터의 툴바 및 이미지 리사이즈 모듈을 설정해주었고,

서버에 먼저 업로드된 이미지를 통해 url을 받아와 배열로 저장하는 이미지 핸들러 함수를 작성했다.

 

 

  • const selectedFiles = input.files: 사용자가 선택한 파일들을 가져옴. input.files는 FileList 객체로, 선택된 파일들의 목록.
  • if (!selectedFiles) return;: 파일이 선택되지 않았을 경우, 함수 실행 종료.
  • let filesArray = Array.from(selectedFiles);: FileList 객체를 배열로 변환. 이는 이후 배열 메서드를 사용하기 위해 필요.
  • setFiles((prevFiles) => [...prevFiles, ...filesArray]);: React의 상태 업데이트 함수. 기존에 업로드된 파일 배열에 새로 선택된 파일들 추가.
  • filesArray.forEach((file, i) => { ... }): 선택된 파일 배열을 순회하면서, 각 파일을 formData 객체에 추가.
  • formData.append('rev_img', file);: formData 객체에 'rev_img'라는 키로 파일을 추가. 이 키는 서버에서 파일을 처리할 때 사용.
  • const res = await axios.post(...): axios를 사용해 백엔드 서버에 이미지 파일을 업로드. 이 과정에서 POST 요청을 보내고, formData 객체를 함께 전송.
  • let imgUrl = res.data.urls;: 서버 응답에서 이미지의 URL을 가져옴. 이 URL은 이미지가 서버에 저장된 위치를 나타냄.

 

const ImageHandler = () => {
    const input = document.createElement('input');
    input.setAttribute('type', 'file');
    input.setAttribute('accept', 'image/*');
    input.click();
    input.onchange = async () => {
      const selectedFiles = input.files;
      if (!selectedFiles) return;
      let filesArray = Array.from(selectedFiles);
      setFiles((prevFiles) => [...prevFiles, ...filesArray]);
      const formData = new FormData();
      filesArray.forEach((file, i) => {
        formData.append('rev_img', file);
      });
      let quillObj = quillRef.current?.getEditor();
      const range = quillObj?.getSelection();
      if (!range) return;
      try {
        const res = await axios.post(`${BACKHOST}/reviews/img`, formData, {
          headers: {
            'Content-Type': 'multipart/form-data',
          },
        });
        let imgUrl = res.data.urls;
        if (Array.isArray(imgUrl)) {
          imgUrl.forEach((url) => {
            quillObj?.insertEmbed(range.index, 'image', url);
          });
        } else {
          quillObj?.insertEmbed(range.index, 'image', imgUrl);
        }
      } catch (error) {
        console.log(error);
      }
    };
  };

 

 

 

서버의 이미지 url을 받아옴으로써 url 길이를 줄일 수 있었고, 정상적으로 DB에 사용자 입력 데이터가 저장되는 것을 확인할 수 있었다.

 

이 과정에서 백엔드 분들과 많은 이야기를 나누면서 필요한 데이터를 요청하고,

수많은 테스트를 진행하며 오류가 나는 부분을 찾고 수정을 요청하면서 가장 많은 소통이 필요했던 과정이었다.

 

 

오류를 해결할 수 있어서 매우 뿌듯했다.

 

리뷰 글 작성

728x90