이번에 구현하게 된 기능은 엑셀 파일 업로드이다.
먼저 input을 정의해주고 onChange 함수 작성
<input type="file" accept=".xlsx, .xls" onChange={handleFileChange} />
const uploadedFileRefPut = useRef<File | null>(null);
const handleFileChange = useCallback(async (event, fileRef) => {
const selectedFile = event.target.files?.[0];
if (selectedFile) {
fileRef.current = selectedFile;
const reader = new FileReader();
reader.onload = async (e: ProgressEvent<FileReader>) => {
if (e.target && e.target.result) {
const data = new Uint8Array(e.target.result as ArrayBuffer);
const workbook = XLSX.read(data, { type: "array", bookVBA: true });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData: ExcelData[] = XLSX.utils.sheet_to_json(worksheet);
}
};
reader.readAsArrayBuffer(selectedFile);
}
}, []);
버튼 클릭 시 업로드를 수행 할 함수 작성
// 엑셀 파일로 일괄 수정
const handlePutExcel = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!uploadedFileRefPut.current) {
toast.error("엑셀 파일을 업로드 해주세요.");
return;
}
const formData = new FormData();
formData.append("file", uploadedFileRefPut.current);
uploadUserExcelPut(formData);
};
최종 구현된 코드는 위와 같지만 초기에 upload할 파일을 useState로 상태를 관리하고 있었다.
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const jsonData: ExcelData[] = XLSX.utils.sheet_to_json(worksheet);
setUploadedFile(jsonData);
}
이렇게 onChange 됐을 때 setUploadedFile로 상태를 업데이트 해 주었음에도 업로드 버튼을 클릭하면 계속 엑셀 파일이 없다는 toast 에러가 뜨는 문제가 발생했다.
디버깅을 해보니 uploadedFile이 null로 찍히고 있었다...
왜 null이지? 나는 분명 setUploadedFile로 업데이트를 해주었는데...
한참 헤맨 끝에 찾은 원인은 uploadedFile이 클로저
이기 때문이었다.
❔ 원인
엑셀 파일 업로드라는 버튼을 클릭하면 input이 있는 모달이 뜨는데 모달이 열린 시점에는 uploadedFile 상태를 클로저로 기억하고 있어서 상태가 업데이트된 이후에도 null 값을 유지하고 있었다.
두 가지의 해결 방법이 있다.
❕방법 1
uploadedFile 상태가 변경될 때마다 handleUpload 함수를 새로 정의하기 위해 useCallback으로 감싸고 의존성 배열에 uploadedFile을 추가하기
const handleUpload = useCallback(async () => {
if (!uploadedFile) {
toast.error("엑셀 파일을 업로드 해주세요.");
return;
}
const formData = new FormData();
formData.append("file", uploadedFile);
await uploadUserExcel(formData);
}, [uploadedFile]);
❕방법 2
useRef를 사용하여 파일 상태를 저장하고 이를 참조하여 항상 최신 상태를 유지하는 방법
위의 최종 코드와 동일
두 방법 모두 상태가 변경될 때마다 handleUpload 함수가 최신 상태를 참조하게 되어 uploadedFile 상태의 동기화를 유지할 수 있게 된다!
나는 상태변경 이후 추가적인 UI의 변경이 없고 리렌더링이 필요하지 않았기 때문에 두 번째 방법으로 구현하기로 했다!
자바스크립트의 기본기가 중요하다는 것을 다시금 느끼게 된 과정이었다.
🧷 참고