웹에 파일을 업로드할 때 Drag & Drop 기능을 제공한다면 사용자 경험을 향상시킬 수 있다.
React에서 Drag & Drop을 이용한 파일 업로드하는 방법을 구현해 보자.
// 부모 컴포넌트
export default function AdVideoUploadContainer() {
// 선택된 파일을 관리한다.
const [file, setFile] = useState<File | null>(null);
// 구현할 InputDragDrop에서 파일이 선택될 때 상태를 업데이트 한다.
const handleFileSelect = (file: File | null) => {
setFile(file);
};
// 파일 업로드를 처리하는 로직
const handleUpload = () => {
if (file) {
// Drag & Drop으로 가져온 파일 처리 로직 (API 호출 등)
}
};
// ...
return
// ...
<InputDragDrop
onChangeFile={handleFileSelect}
description="등록하실 비디오를 선택하거나 올려주세요."
></InputDragDrop>
// ...
interface DragDropProps {
onChangeFile: (file: File | null) => void;
description?: string;
}
const DragDrop = ({
onChangeFile,
description = "파일 첨부",
}: DragDropProps) => {
// 사용자가 파일을 드래드 중임을 상태로 관리 UI 변경을 위해 사용
const [dragOver, setDragOver] = useState<boolean>(false);
// 드래그 중인 요소가 목표 지점 진입할때
const handleDragEnter = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
setDragOver(true);
};
// 드래그 중인 요소가 목표 지점을 벗어날때
const handleDragLeave = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
setDragOver(false);
};
// 드래그 중인 요소가 목표 지점에 위치할때
const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
};
// 드래그 중인 요소가 목표 지점에서 드롭될때
const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
setDragOver(false);
// 드래그되는 데이터 정보와 메서드를 제공하는 dataTransfer 객체 사용
if (e.dataTransfer) {
const file = e.dataTransfer.files[0];
onChangeFile(file);
}
};
// Drag & Drop이 아닌 클릭 이벤트로 업로드되는 기능도 추가
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files ? e.target.files[0] : null;
onChangeFile(file);
// input 요소의 값 초기화
e.target.value = "";
};
return (
<div className="flex flex-col justify-center items-center w-full">
<div className="w-3/4">
<Label
className={`w-full flex-col gap-3 h-32 border-2 ${
dragOver
? "border-blue-500 bg-blue-100 text-blue-500 font-semibold"
: "border-gray-300"
} rounded-md flex items-center justify-center cursor-pointer`}
htmlFor="fileUpload"
// Label에 드래그 앤 드랍 이벤트 추가
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{description}
<div className="w-9 h-9 pointer-events-none">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1}
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</div>
</Label>
<Input
id="fileUpload"
type="file"
className="hidden"
onChange={handleChange}
></Input>
</div>
</div>
);
};
export default DragDrop;
// InputDragDrop 컴포넌트
interface InputDragDropProps {
onChangeFile: (file: File | null) => void;
description?: string;
validExtensions?: string[]; // 확장자 정보를 받아온다.
}
const InputDragDrop = ({
onChangeFile,
description = "파일 첨부",
validExtensions = ["*"], // 디폴트는 모든 확장자 허용.
}: InputDragDropProps) => {
const { toast } = useToast(); // 알림 컴포넌트
// ... 기존 코드
// 파일 확장자 검증 함수
const isValidExtension = (file: File) => {
const fileName = file.name;
const fileNameSplit = fileName.split(".");
const fileExtension = fileNameSplit[fileNameSplit.length - 1];
return validExtensions.includes(fileExtension);
};
// 허용된 확장자라면 업로드, 아니라면 사용자에게 알림
const handleFileChange = (file: File | null) => {
if (file && isValidExtension(file)) {
onChangeFile(file);
} else {
toast({
title: "잘못된 파일 형식",
description: `지원하지 않는 파일 형식입니다. (${validExtensions.join(
", "
)})로 등록해주세요.`,
className: "bg-red-500 text-white",
});
onChangeFile(null);
}
};
// ...기존 코드
// 부모 컴포넌트
return
// ...
<InputDragDrop
onChangeFile={handleFileSelect}
description="등록하실 비디오를 선택하거나 올려주세요."
validExtensions={["mp4"]} // 확장자 정보 추가
></InputDragDrop>
// ...