본문 바로가기
프로그래밍/웹 개발

[React] Button 통한 파일 업로드 및 진행상황(progress) 표시

by 제이콥J 2023. 4. 12.

input 태그만 사용 시 문제점

<input type="file" />

 

파일 업로드 전 화면
파일 업로드 후 화면

 

위 코드처럼 input 태그를 사용하면, 파일 업로드를 위한 UI가 화면에 생성됩니다.

그러나 위 UI를 화면에 표시하고 싶지 않은 경우가 있습니다.

input 엘리먼트를 화면에서 숨기되, Button 엘리먼트를 통해서 파일을 업로드하는 코드를 구현해보겠습니다.

 

 

파일 업로드 코드 구현

1. display:none 스타일 속성으로 input 엘리먼트를 화면에서 숨깁니다.

<input
  type="file"
  ref={inputRef}
  style={{ display: "none" }}
  onChange={handleFileChange}
/>

 

2. useRef를 사용하여 <input>을 참조하고, 버튼 클릭 시 <input>에 대한 클릭이벤트가 발생하도록 합니다.

useRef를 사용하면 버튼 엘리먼트에서 onClick 이벤트가 발생했을 때 input 태그가 동작하도록 만들 수 있습니다.

input 태그에 클릭 이벤트를 발생시키는 함수를 제작하고, 이를 버튼 엘리먼트의 onClick 이벤트 핸들러 함수로 추가합니다.

 

const inputRef = useRef<HTMLInputElement>(null);
  
const handleUploadButtonClick = () => {
  if (inputRef.current) {
    inputRef.current.click();
  }
};

...
<button
  disabled={isUploading}
  className="file-upload"
  onClick={handleUploadButtonClick}
>
  파일 업로드
</button>

 

3. <input>의 onChange 이벤트를 통해 파일 정보를 state에 저장합니다.

file 데이터를 저장할 state를 선언합니다.

input 엘리먼트에 onChange 이벤트함수를 등록하여 업로드된 파일을 state에 저장합니다.

 

const [file, setFile] = useState<File | undefined>(undefined);

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  if (event.target.files && event.target.files[0]) {
    setFile(event.target.files[0]);
  }
};
<input
  type="file"
  ref={inputRef}
  style={{ display: "none" }}
  onChange={handleFileChange}
/>

 

4. axios를 통해 파일 데이터, multipart/form-data 헤더, onUploadProgress 콜백함수를 전달합니다.

파일 데이터를 AJAX 요청으로 서버에 전달하는 함수를 제작합니다.

 

axios의 request body에 file 데이터를 입력해줍니다.

 

헤더의 content-type은 body 데이터의 종류를 명시하며 여기서는 multipart/form-data를 입력합니다.

multipart/form-data는 요청에서 바이너리 데이터를 인코딩하는 방법으로

바이너리 데이터가 포함된 파일을 업로드하거나 양식을 제출할 때 사용됩니다.

 

axios의 onUploadProgess 콜백함수를 통해 진행상황에 대한 정보를 받아 화면에 표시할 수 있습니다.

onUploadProgress 함수는 파일 업로드 중에 주기적으로 호출되며 업로드 진행률에 대한 정보가 포함된 이벤트 객체를 받습니다.

 

const [file, setFile] = useState<File | undefined>(undefined);
const [isUploading, setIsUploading] = useState<boolean>(false);
const [progressPercent, setProgressPercent] = useState<number>(0);

const handleUploadButtonClick = () => {
if (inputRef.current) {
    inputRef.current.click();
  }
};

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files[0]) {
    setFile(event.target.files[0]);
  }
};

const handleSubmitFile = async () => {
if (file) {
    const formData = new FormData();

    formData.append("file", file); // formData에 file 데이터 넣어주기 (key값은 "file"이지만 다른 값으로 변경 가능. 단 서버와 코드를 맞춰야 함)

    try {
    setIsUploading(true);

    const headers = {
        "Content-Type": "multipart/form-data",
    };
    const onUploadProgress = (event: ProgressEvent) => {
        const per = (event.loaded / event.total) * 100;
        setProgressPercent(parseFloat(per.toFixed(2)));
    };

    await axios.post(`${serverURL}/api/upload`, formData, {
        headers,
        onUploadProgress,
    });

    setFile(undefined);
    setIsUploading(false);
    } catch (err) {
    setFile(undefined);
    setIsUploading(false);
    alert("파일 업로드중 문제가 발생하였습니다");
    }
  }
};

 

5. 파일이 전송중인 경우 진행상황 % 데이터를 화면에 표시합니다.

<progress
style={{
    display: `${isUploading ? "block" : "none"}`,
}}
>
  {progressPercent}%
</progress>

 

 

전체 코드

import axios, { AxiosResponse } from "axios";
import serverURL from "../utils/ServerURL";
import React, { useEffect, useRef, useState } from "react";

export default function Test() {
  const [file, setFile] = useState<File | undefined>(undefined);
  const [isUploading, setIsUploading] = useState<boolean>(false);
  const [progressPercent, setProgressPercent] = useState<number>(0);
  const inputRef = useRef<HTMLInputElement>(null);

  const handleUploadButtonClick = () => {
    if (inputRef.current) {
      inputRef.current.click();
    }
  };

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files && event.target.files[0]) {
      setFile(event.target.files[0]);
    }
  };

  const handleSubmitFile = async () => {
    if (file) {
      const formData = new FormData();

      formData.append("file", file);
      // formData에 file 데이터 넣어주기 (key값은 "file"이지만 다른 값으로 변경 가능. 단 서버와 코드를 맞춰야 함)

      try {
        setIsUploading(true);

        const headers = {
          "Content-Type": "multipart/form-data",
        };
        const onUploadProgress = (event: ProgressEvent) => {
          const per = (event.loaded / event.total) * 100;
          setProgressPercent(parseFloat(per.toFixed(2)));
        };

        await axios.post(`${serverURL}/api/upload`, formData, {
          headers,
          onUploadProgress,
        });

        setFile(undefined);
        setIsUploading(false);
      } catch (err) {
        setFile(undefined);
        setIsUploading(false);
        alert("파일 업로드중 문제가 발생하였습니다");
      }
    }
  };

  useEffect(() => {
    if (file) {
      handleSubmitFile();
    }
  }, [file]);
  return (
    <>
      <button
        disabled={isUploading}
        className="file-upload"
        onClick={handleUploadButtonClick}
      >
        파일 업로드
      </button>
      <input
        type="file"
        ref={inputRef}
        style={{ display: "none" }}
        onChange={handleFileChange}
      />
      <progress
        style={{
          display: `${isUploading ? "block" : "none"}`,
        }}
      >
        {progressPercent}%
      </progress>
    </>
  );
}
반응형

댓글