사진 업로드 코드
import styled from '@emotion/styled';
import { AxiosResponse } from 'axios';
import React, { SetStateAction, useEffect, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import theme from '../theme';
import Camera from '/src/images/icon/camera.svg';
import Delete from '/src/images/icon/deleteImage.svg';
import Loading from '/src/images/icon/loading.gif';
import useSnackBar from '../hooks/useSnackBar';
import uploadImage from '../axios/apis';
type ImageInfo = {
id: string;
uri: string;
originName: string;
};
type PreviewProps = {
imageInfoList: ImageInfo[];
setImageInfoList: any;
};
class CustomError extends Error {
type: string;
constructor(message: string, type: string) {
super(message); // (1)
this.type = type;
}
}
function Preview({ imageInfoList, setImageInfoList }: PreviewProps) {
// 파일 업로드를 위한 <input>태그 ref 지정
const inputRef = useRef<HTMLInputElement | null>(null);
// const [imageInfoList, setImageInfoList] = useState<ImageInfo[]>([]);
const [isUploading, setIsUploading] = useState(false);
const { showSnackBar } = useSnackBar();
const handleUploadingImage = () => {
setIsUploading(true);
};
const handleFailUploadImage = (e: CustomError | unknown) => {
setIsUploading(false);
if (e.type === "FILELIST_LEN_ERROR") {
showSnackBar({
snackBarState: true,
snackBarType: 'negative',
snackBarMessage: '사진은 5개 이하로 선택해주세요',
});
return;
}
// if (e.type == 'snackbar') {
// show snackbar
// }
};
const handleUploadImage = (
e: any,
onFail: (e: Error | unknown) => void,
onSuccess: () => void,
onUploading: () => void
) => {
if (!e.target.files) {
// dodododo
return;
}
const fileList = Array.from(e.target.files);
try {
onUploading();
if (fileList.length === 0) throw new CustomError('', '');
if (imageInfoList?.length + fileList.length > 5 || fileList.length > 5) {
throw new CustomError('','FILELIST_LEN_ERROR');
}
onSuccess();
} catch (e: CustomError | unknown) {
onFail(e);
} finally {
// any
}
};
const onUploadImage = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsUploading(true);
if (!e.target.files) {
return;
}
// 유사배열 객체 이므로 얕은 복사로 배열로 만들어줌.
const fileList = Array.from(e.target.files);
if (fileList.length === 0) {
setIsUploading(false);
return;
}
// 한번에 5개이상 올릴려고 하면 snack bar + 4개 올리고 추가로 2개 더 올릴려고하면 snack bar
if (imageInfoList?.length + fileList.length > 5 || fileList.length > 5) {
showSnackBar({
snackBarState: true,
snackBarType: 'negative',
snackBarMessage: '사진은 5개 이하로 선택해주세요',
});
setIsUploading(false);
return;
}
fileList.forEach(async (file) => {
const maxSize = 3 * 1024 * 1024;
const fileSize = file.size;
const formData = new FormData();
// 파일 사이즈 체크
if (fileSize > maxSize) {
showSnackBar({
snackBarState: true,
snackBarType: 'negative',
snackBarMessage: '사진 용량은 3MB 이하로 업로드 해주세요',
});
return;
}
formData.append('file', file);
formData.append('type', 'COMMUNITY');
try {
const response = await uploadImage(formData);
console.log(response);
if (response) {
const data = response.data;
setImageInfoList((prev: ImageInfo[]) => [
...prev,
{
id: data.id,
uri: data.uri,
originName: data.originName,
},
]);
}
} catch (err) {
console.log(err);
showSnackBar({
snackBarState: true,
snackBarType: 'negative',
snackBarMessage: '사진 업로드에 실패 하였습니다.',
});
return;
} finally {
setIsUploading(false);
}
});
};
// 사진 미리보기 삭제
const handleDeleteImage = (id: string) => {
const copiedImageInfoList = [...imageInfoList];
const filteredImageInfoList = copiedImageInfoList.filter(
(imageInfo: ImageInfo) => imageInfo.id !== id
);
setImageInfoList(filteredImageInfoList);
};
return (
<>
<PreviewTitle>사진 첨부 (최대 {imageInfoList?.length}/5장)</PreviewTitle>
<UploadImageContainer>
<PhotoLabel htmlFor="file">
<Icon>
<img src={isUploading ? Loading : Camera} alt="카메라 아이콘" />
</Icon>
</PhotoLabel>
<PhotoInput
type="file"
id="file"
multiple
disabled={isUploading ? true : false}
accept=".jpg,.jpeg,.png,.gif,.tif,.tiff,.bmp"
placeholder="첨부파일"
ref={inputRef}
onChange={onUploadImage}
/>
<PhotoList>
{imageInfoList?.map((imageInfo: ImageInfo) => (
<React.Fragment key={imageInfo.id}>
<PhotoItem>
<img src={imageInfo.uri} alt={imageInfo.originName} />
<DeleteIcon onClick={() => handleDeleteImage(imageInfo.id)}>
<img src={Delete} alt="삭제 아이콘" />
</DeleteIcon>
</PhotoItem>
</React.Fragment>
))}
</PhotoList>
</UploadImageContainer>
</>
);
}
const UploadImageContainer = styled.div`
display: flex;
margin-bottom: 40px;
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
`;
const PreviewTitle = styled.h1`
margin-bottom: 16px;
${theme.fontStyle.font16};
color: ${theme.color.primary};
`;
const PhotoLabel = styled.label`
flex-shrink: 0;
margin-right: 16px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
border: 1px dashed ${theme.color.border};
border-radius: 10px;
&:hover {
cursor: pointer;
}
`;
const PhotoInput = styled.input`
display: none;
`;
const Icon = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
img {
display: block;
width: 100%;
height: auto;
}
`;
const PhotoList = styled.ul`
display: flex;
`;
const PhotoItem = styled.li`
position: relative;
flex-shrink: 0;
width: 80px;
height: 80px;
margin-right: 16px;
border: 1px solid ${theme.color.border};
border-radius: 10px;
overflow: hidden;
background-color: ${theme.color.border};
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
&:last-child {
margin-right: 0;
}
`;
const DeleteIcon = styled.div`
position: absolute;
top: 0;
right: 0;
width: 24px;
height: 24px;
img {
display: block;
width: 100%;
height: auto;
}
`;
export default Preview;
catch에서 인자로 받은 error 객체 타입지정
[typescript] Object is of type 'unknown'.ts(2571) (error object)
useMutation
[React] React Query의 useMutation에 대해 알아보기
react-query