ChatGPT랑 비슷한 File Input 컴포넌트 구현하고 react-hook-form에 연동하기(파일 누적 관리하기)

오늘은 ChatGPT에서 사용하는 파일 인풋을 유사하게 구현했습니다. ChatGPT에서는 파일을 입력할때마다 업로드를 하기 때문에 실제 처리 방식은 다르지만 파일들을 모아서 한번에 업로드 해야 한다고 가정했습니다. 즉, 여러 파일을 선택하고 폼에 누적해서 관리하는 기능을 구현했습니다. 아래에 ChatGPT의 인풋 컴포넌트를 가져왔습니다.

ChatGPT 인풋 컴포넌트

구현사항

  1. 파일을 입력하면 미리보기로 보여줬습니다.
  2. 기존 file input과 다르게 파일을 여러번에 걸쳐 추가로 입력해도 누적이 되도록 했습니다.
  3. react-hook-form을 연동하여 form을 관리했습니다.
import React, { useState } from 'react'
import { useForm, Controller } from 'react-hook-form'

const FileInputForm = () => {
  const { handleSubmit, control, setValue, getValues } = useForm()
  const [filesArray, setFilesArray] = useState([])

  const onSubmit = data => {
    console.log('Submitted files:', getValues('files'))
  }

  const handleFileChange = event => {
    const selectedFiles = Array.from(event.target.files)

    if (selectedFiles) {
      // Array.from에 넣는 이유는 selectedFiles가 array가 아닌 FileList이기 때문입니다.
      const selectedFilesArray = Array.from(selectedFiles)

      // 보여주는 파일 리스트 정보 처리
      const newFileInfoList = selectedFilesArray.map(file => ({
        name: file.name,
        size: `${(file.size / 1024).toFixed(2)} KB`,
        url: URL.createObjectURL(file),
      }))
      setFileInfoList(prevFileInfoList => [
        ...prevFileInfoList,
        ...newFileInfoList,
      ])

      // files form value 처리
      // 기존 파일 목록과 새로 선택한 파일 목록을 합칩니다.
      // 이렇게 처리하지 않으면 새로 선택한 파일이 기존 파일을 대체합니다.
      const currentFiles = Array.from(getValues('files') || [])
      const updatedFiles = [...currentFiles, ...selectedFilesArray]

      // array에 담긴 정보를 가지고 FileList를 만듭니다.
      const fileList = new DataTransfer()
      updatedFiles.forEach(file => fileList.items.add(file))
      setValue('files', fileList.files)
    }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="files"
        control={control}
        defaultValue={[]}
        render={() => (
          <input type="file" onChange={handleFileChange} multiple />
        )}
      />
      <ul>
        {filesArray.map((file, index) => (
          <div key={index}>
            <img src={file.url} alt={file.name} />
            <li>{file.name}</li>
            <li>{file.size}</li>
          </div>
        ))}
      </ul>
      <button type="submit">Upload</button>
    </form>
  )
}

export default FileInputForm

ChatGPT를 사용할 때는 저런걸 어떻게 보여주지 생각했었는데 실제로 구현해보니까 신기했습니다. 보통 이렇게 누적식으로 관리해본 적은 처음이었고, n번째 파일을 제거할 때 DataTransfer api를 사용해서 FileList를 다시 만드는 게 인상적이었습니다.


Written by@Donghoon Song
사람들의 꿈을 이어주는 코멘토에서 일하고 있습니다.

InstagramGitHubTwitterLinkedIn