[react-hook-form] 프로필 이미지 업로더

비얌·2024년 2월 21일
1
post-thumbnail

✨ 개요

최근 아래처럼 트위터 계정의 프로필 정보를 불러오는 폼을 만들고 있다. 이때 불러온 프로필 이미지를 사용자가 직접 변경할 수 있게 하는 기능을 추가했다!

그래서 프로필 이미지를 업로드 할 수 있는 기능에 대해서 포스팅해보려고 한다.

react-hook-form으로 폼을 관리하고 있었기에 react-hook-form을 활용해서 만들기로 했다. 프로필 이미지 업로더를 만들 때 아래 링크를 참고했다.

참고: https://dreamix.eu/insights/uploading-files-with-react-hook-form/



👏 결과 미리보기

결과부터 미리 보자면, 이렇게 선택한 이미지로 프로필을 업데이트 할 수 있다.



🧑‍💻 코드

프로필 사진을 업로드하는 부분의 코드는 총 세 영역으로 나누어져있다.

  1. 프로필 사진이 보이는 부분

    • 로컬에서 이미지 파일을 선택하면 url로 바꿔주는 코드를 거쳐 파일이 url로 바뀐다. 이를 img 태그에 src로 경로를 넣어주면 화면에 이미지가 보인다.
  2. 업로드할 이미지를 선택하는 부분

    • 화면에는 보이지 않는다. (display: none)
    • 화면에서 안보이게 처리한 이유는 기본적으로 제공되는 파일 선택 필드라서 예쁘지 않기 때문이다. 대신 커스텀해서 따로 만든 버튼을 누르면 이 필드가 선택되도록 했다. (hiddenInputRef.current?.click())
  3. 이미지 변경하기 버튼

    • 이 버튼의 onClick 이벤트가 실행되면 가려둔 부분이 선택되며 로컬에서 이미지를 가져올 수 있다.
import { useRef } from 'react';
import { useForm } from 'react-hook-form';

const hiddenInputRef = useRef<HTMLInputElement | null>(null);
const twitterImage = watch('twitterImage');

// ref를 register에서 따로 꺼내기
const { ref: registerRef, ...rest } = register('twitterImage');

const fileUploadHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
  // 업로드한 파일 가져오기
  const file = e.target.files?.[0];

  if (file) {
    const binaryData = [file];
    const urlImage = URL.createObjectURL(new Blob(binaryData,{ type: 'image' }));

    // 이미지 URL을 src로 설정
    setValue('twitterImage', urlImage);
  }
};

return (
  {/* 프로필 사진이 보이는 부분 */}
  {twitterImage && <img src={twitterImage} alt="프로필 이미지" className="w-40 h-40" />}

  {/* 업로드할 이미지를 선택하는 부분 */}
  <input
    {...rest}
    type="file"
    ref={(e) => {
    // registerRef를 통해 react-hook-form이 이 input의 ref를 추적할 수 있다
    registerRef(e);
    // hiddenInputRef를 사용하여 외부에서 이 input에 접근할 수 있다
    hiddenInputRef.current = e;
  }}
    className="hidden input h-10 w-full max-w-xs shadow-sm placeholder:text-xs"
    name="twitterImage"
    onChange={fileUploadHandler}
    />

  {/* 이미지 변경하기 버튼 부분*/}
  <button
    type="button"
    className="btn btn-success text-white btn-sm text-xs w-1/2 mt-1"
    onClick={() => {
      hiddenInputRef.current?.click();
    }}
    >
    이미지 변경하기
  </button>
)


💥 시행착오

💥 이미지를 변경하면 폼이 자동으로 제출됨

이미지를 변경하면 자동으로 폼이 제출되는 오류가 있었다. 그래서 차근차근 살펴보았다.
1. fileUploadHandler 안의 코드를 주석처리 해보았을 때, 이 문제는 아니었다.

const fileUploadHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log('fileUploadHandler 실행');
  // // 업로드한 파일 가져오기
  // const file = e.target.files?.[0];

  // // 파일을 로컬에서 url로 변환
  // // TODO: 서버에 프로필 파일 업로드해서 URL 받아오기
  // if (file) {
  //   const binaryData = [file];
  //   const urlImage = URL.createObjectURL(new Blob(binaryData, { type: 'image' }));

  //   // 이미지 URL을 src로 설정
  //   setValue('twitterImage', urlImage);
  // }
};
  1. 이미지 변경하기 버튼의 onClick 이벤트를 주석처리해도 자동으로 제출이 됐다. 이때 이벤트 핸들러 문제도 아니니, button 자체에 문제가 있는 것으로 문제가 좁혀졌다.
<button
  className="btn btn-success text-white btn-sm text-xs w-1/2 mt-1"
  // onClick={() => {
  //   hiddenInputRef.current?.click();
  // }}
  >
  1. button 속성으로 type=button이라고 안써줘서 자동으로 제출이 됐던 거였다. button은 따로 설정하지 않아도 기본적으로 type=submit 속성을 가지고 있기 때문이다.
<button
  type="button"
  className="btn btn-success text-white btn-sm text-xs w-1/2 mt-1"
  onClick={() => {
    hiddenInputRef.current?.click();
  }}
  >

💥 하나의 input에 두개의 ref를 활용하는 것

하나의 input에 두개의 ref가 접근하는 것이 이해가 가지 않았다. 지금도 정확히 이해가 가지는 않지만 아래처럼 이해하고 있다.

  • registerRef는 react-hook-form이 input 입력 요소를 관리하기 위해 쓰인다
  • hiddenInputRef는 외부에서 input에 접근할 수 있게 하기 위해 쓰인다.
<input
  {...rest}
  type="file"
  ref={(e) => {
    registerRef(e);
    hiddenInputRef.current = e;
  }}
  onChange={fileUploadHandler}
  />

💥 버튼 외부의 영역을 눌렀을 때도 파일 선택이 됨

버튼 뿐만 아니라 버튼 외부에 있는 이미지나 제목을 눌렀을 때도 파일을 선택하는 창이 열리는 오류가 있었다. 이는 라벨로 제목만이 아니라 이미지와 버튼까지 잘못 감싸고 있어서 생기는 오류였다.



✨ 결과

로컬에 있는 이미지를 선택하여 프로필 이미지를 변경하는 것에 성공했다!



🐹 회고

폼을 관리하기 위한 라이브러리 react-hook-form을 최근 배우기 시작했다. 실제로 폼을 만들면서 직접 사용해보며 많이 알아가고 있는 것 같아 기쁘다!

react-hook-form으로 프로필 이미지를 업로드하는 포스팅을 한국어로는 찾지 못해서 자세하게 써보았다. 다음에는 이번에 만든 프로필 이미지 업로더 부분을 포함하는 전체 폼에 대해서 포스팅해보고 싶다.

profile
🐹강화하고 싶은 기억을 기록하고 공유하자🐹

0개의 댓글

관련 채용 정보