작성
·
3.7K
·
수정됨
1
안녕하세요 선생님..그냥 단순히 인풋박스 만들어서 이미지 업로드를 해도 되지만 antd의 upload가 예뻐보여서 이걸 써서 이미지를 업로드 하고 싶은데 아무리 해도 되질 않아서 질문 드립니다ㅠㅠ
보니까 보통 인풋박스를 통해 이미지 파일을 업로드할 때 양식이 FileList형식(배열처럼 보이는 객체...?)이던데
antd upload에서 제공하는 props의 filelist는
양식이 좀 다르더라구요..
일단 백은 기존의 이미지 업로드 방식에서 바뀐 부분이 없고,
제가 antd의 Upload를 사용한 방식은 아래와 같습니다.
<Upload
name='image'
listType="picture-card"
multiple
fileList={fileList}
onChange={onChangeImages}
onPreview={onPreview}
onRemove={onRemoveImage}
>
{fileList.length < 2 && 'Drag images or click'}
</Upload>
업로드 props는 위와 같이 설정하고 onChange함수는
const onChangeImages = useCallback(({ fileList: newFileList }) => {
setFileList(newFileList);
console.log('images', fileList);
const imageFormData = new FormData();
for(let i = 0; i< fileList.length; i++){
imageFormData.append('key',fileList[i]);
}
return dispatch({
type: UPLOAD_IMAGES_REQUEST,
data: imageFormData
});
});
위와 같이 작성하면 업로드 되지 않을까 했는데, 이미지 업로드가 성공했다고 쓰지만
콘솔 창에 나온 메세지를 보면
{type: 'UPLOAD_IMAGES_SUCCESS', data: Array(0), @@redux-saga/SAGA_ACTION: true}
이런 식으로 빈 배열만 자꾸 들어가고, 콘솔로 찍어본 fileList는 아래와 같이 뜨던데
images
[{…}]
0:
lastModified: 1669296034457
lastModifiedDate: Thu Nov 24 2022 22:20:34 GMT+0900 (한국 표준시) {}
name: "icons8-team-URoyGsQdLwg-unsplash.jpg"
originFileObj: File {uid: 'rc-upload-1675523328622-3', name: 'icons8-team-URoyGsQdLwg-unsplash.jpg', lastModified: 1669296034457, lastModifiedDate: Thu Nov 24 2022 22:20:34 GMT+0900 (한국 표준시), webkitRelativePath: '', …}
percent: 0
size: 674143
status: "uploading"
thumbUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMg
type: "image/jpeg"
uid: "rc-upload-1675523328622-3"
[[Prototype]]: Object
length: 1
[[Prototype]]: Array(0)
노드버드에서는 업로드할 파일을 콘솔에 찍어보았을 때
FileList {0: File, length: 1}
0: File
lastModified: 1669296034457
lastModifiedDate: Thu Nov 24 2022 22:20:34 GMT+0900 (한국 표준시) {}
name: "icons8-team-URoyGsQdLwg-unsplash.jpg"
size: 674143
type: "image/jpeg"
webkitRelativePath: ""
[[Prototype]]: File
length: 1
[[Prototype]]: FileList
이렇게 나오는걸 보면 데이터 구조상의 차이가 있어서 업로드가 안되는 것인가요?
antd upload를 이용한 파일 업로드를 할 때 제가 놓친 부분이 어디인지 알 수 있을까요..? 조언 부탁드립니다 선생님ㅠ
답변 1
0
아 선생님 말씀 듣고 다시 해보니까 됐어요 감사합니다! 그런데 선생님 질문이 두가지 더 있는데,
리듀서switch문 case이름이 같으면 다른 디렉토리에 있는 같은 이름의 case들도 같이 작동하나요? 댓글 이미지 onChange로 업로드 할 때 요청이 3번이나 가가지고...같은 동작을 하더라도 다 따로 만들어야 하는 건가요?
댓글에서 upload로 업로드 할 때 이미지를 최대 2개 보내게 하려고
{fileLists.length < 2 && 'Drag images or click'}
이걸 해놨는데 여태 잘 되더니 갑자기 댓글 submit을 하고 나면
TypeError: Cannot read properties of undefined (reading 'length')
라고 뜨는데 이건 왜 그런 건가요? 댓글 업로드 하기 전 콘솔 창에
console.log("파일리스트 길이", fileLists.length);
이렇게 찍어보면
이렇게 찍히는데도 위와 같은 에러가 떠서 이해를 할 수가 없어서 질문 드립니다ㅠ
아뇨 딱히 fileList를 undefined로 바꾸는 식은 사용 안했는데 당최 왜이런건지 모르겠습니다
useState 사용할 때 fileList를 빈 배열로
const [fileList, setFileList] = useState([]);
이렇게 정의하고
onChange에서는 2개 다 채워도 length를 모른다는 말이 안뜨는데 submit을 하고 나면 뜨더라고요...onChange 함수와 구조도 차이가 없는데,
아래와 같이 sumbit 함수에서 for문으로 append하고 데이터를 전송하는데 왜 이런지 아직도 이유를 찾지 못했습니다 선생님...ㅠ
const onSubmit = useCallback(({ fileList: newFileList }) => {
const formData = new FormData();
setFileList(newFileList);
for(let i = 0; i< fileList.length; i++){
formData.append('image', fileList[i].originFileObj);
}
// ...나머지 코드 생략...
dispatch({
type: ADD_COMMENT_REQUEST,
data: formData
});
}, [content, value, imagePaths]);
그리고 선생님...혹시 antd calendar로는 YYYY-MM-DD형식의 데이터를 렌더링 할 수는 없나요?
antd calendar 공식문에서 나와있는 대로 switch문을 이용해 날짜별로 데이터를 렌더링 하려고 갖은 방법은 다 써봤는데, 날짜를 박아넣으려고 해도
이런 식으로 자꾸 전체 일자로 데이터가 랜더링 되는데...이미지가 2개 붙어 나오는 것은 해당 아이디의 게시글이 작성일자가 다르게 2개가 있는데, 그 게시글의 이미지가 붙어나온 것입니다.
antd calendar에서의 프롭스는
<Calendar
style={{width: 700, height: 893}}
dateCellRender={DateCellRender}
/>
위와 같이 설정하고, DateCellRender는 별도의 컴포넌트로 만들어서 import 하였습니다.
그래서 DateCellRender 함수에서는
로그인된 아이디의 게시글을 전체로 불러와서
moment와 format을 사용해 캘린더에 넣을 createdAt을 YYYY-MM-DD형식으로 바꾸고
for문을 통해 게시글의 갯수 만큼 정의한 배열에 캘린터에 렌더링할 데이터(게시글 제목, 이미지)를 push로 집어넣고
(배열을 콘솔로 찍은 결과는
[{"photo":"yiran-ding-URn7-JupQ6Q-unsplash_1675759973532.jpg","title":"첫번째","date":"2023-02-06","id":18},{"photo":"icons8-team-URoyGsQdLwg-unsplash_1675763357814.jpg","title":"두번째","date":"2023-02-07","id":19}]
위와 같습니다)
)
antd calendar에서 사용한 방식대로 switch문을 이용해 게시글이 작성된 날짜에만 게시글 제목과 이미지가 렌더링 되도록 한다.
를 시도했는데 위에 보여드렸던 대로 전체 일자에 렌더링 되고...그렇습니다ㅠㅠ
switch(new Date(date).getDate()){
case new Date(date).getDate():
return (
<Link href="#">
<img style={{width: 50}} src={`http://localhost:3065/${photo}`} />
</Link>
);
default:
return(
<div></div>
);
}
이런식으로 moment말고 Date메소드도 써서 어떻게든 데이터를 박아넣어보려고 시도해봐도 잘 안되더라구요..결과가 동일해서...(위의 date에는 게시글 작성일인 createdAt을 손보지 않고 그대로 넣었습니다 이 형식이 Date메소드를 가장 안정적으로 쓸 수 있다고 해서요)
제가 놓친 부분이 있거나 제대로 알지 못한 부분이 있다면 조언 부탁드립니다 선생님........ㅠㅠ
일단 filelist 관련해서는 react devtool Component 메뉴에서 prop이 어떻게 바뀌어가는지 추적해보시면 어느 순간 undefined로 바뀌고 있을 겁니다. 그걸 잡아내시면 되고요.
근데 일단 TypeError: Cannot read properties of undefined (reading 'length') 이 에러가 filelist에서 나는 건 맞나요?
지금 저 switch문은 아무 의미가 없는 switch 문입니다. 게시물 작성 날짜와 캘린더의 날짜랑 비교해야지 게시물 작성 날짜와 게시물 작성 날짜를 비교하면 항상 참이니까요.
아........캘린더 건은 제가 공식문서를 볼 때 렌더링 함수의 기본 제공 매개변수를 의미없는거라 착각하고 무시해서 일어난 사태였어요.......멍청해서 시간만 왕창 날려먹었네요ㅠ
선생님 말씀대로 switch문에서 캘린더 날짜와 게시글 날짜 비교하니까 잘되네요...감사합니다!
그리고 fileList.length에러 건은 일단 콘솔에서 가리키는 에러 위치가
리턴문 안, <Upload>태그 사이인
{ fileList.length < 2 && 'Drag images or click' }
위의 코드를 가리키고 있습니다.
말씀해주신 대로 리덕스 컴포넌트에서 프롭스 변화 과정을 추적해보려고 콘솔 창에 로그를 띄워주는 디버깅 기능을 써보니까
이렇게 나오는걸 확인했지만 저는 여태 에러를 콘솔창이나 터미널의 에러 스택을 보고 해결해왔어서 props추적을 어떻게 해야 하는지...제가 아는 바가 너무 적어서 pops trace(명칭이 이게 맞나요?)하는 법에 대해 알아내려면 어떤 키워드로 검색하면 되는지 조언 부탁드립니다 선생님
일단 에러는 어...
{ fileList ? fileList.length < 2 && 'Drag images or click' : null}
이런 식으로 틀어막아놨..다고 해야 하나.. 이렇게 해두기는 했습니다ㅠ
감사합니다 선생님! 말씀하신 대로 props 에러 배열을 쭉 보고 있는데, 혹시 methodName: "<unknown>"인 부분이 문제를 일으키는 건가요?
error가 true인 객체를 열어보니까 마지막으로 호출된 컴포넌트를 제외하고 다른 컴포넌트에서 methodName: "<unknown>"인 것을 2개 발견했는데
아래와 같이
이렇게 나오고, file은 "file://C:/Users/내컴/Desktop/fashionary/front/.next/static/chunks/pages/post/%5Bid%5D.js" 이렇게 들어있던데 경로가 게시글 개별 페이지 컴포넌트인 [id].js를 가리킨다는 것 까지는 알겠는데 여기서 어떤 것을 더 봐야 하나요?
선생님 혹시 prop 추적할 때 시점이 에러가 난 때가 아니라 에러 나기 전 시점을 봐야 하는건가요? 보여드린 화면이 에러가 난 후여서...
에러 나기 전에는 submit 전, 이미지를 올렸을 때
<Upload>태그에선
위와 같이 state가 drop이고
이 컴포넌트(올린 이미지를 클릭했을 때 이 컴포넌트를 가리켰습니다)는
위와 같이 state가 done인 상태인데 이렇게 보는거 맞나요..?
당연히 전과 후를 비교해야 하는 것이고요. filelist state가 언제 undefined가 되는지를 보세요. filelist state가 Upload에 있지 않잖아요.
왜 Upload를 보시는지를 이해하지 못하겠습니다. filelist state가 undefined여서 발생한 에러 아닌가요? 그럼 filelist state를 보셔야죠. filelist state는 Uplpad가 아니라 그 부모의 state잖아요.
헉 네 fileList를 useState로 빈배열로 선언해놓고 왜 state를 안봤을까요..ㅠㅠㅠ
선생님 말씀대로 submit하기 전 append 해놓은 상태에선 아래 사진과 같이 state가 올바르게 바뀐걸 볼 수 있고
에러가 난 시점에서는 화면이 컴포넌트들이 아래와 같이 보이는데
위 화면의 state는 제가 찾는 fileList의 state가 아닌것 같은데...submit후 state의 변화를 보려면 어디를 봐야 하나요? submit후에는 화면이 바뀌어서 추적할 state가 있는 컴포넌트가 보이질 않아서...ㅠㅠㅠ
이렇습니다
import { HeartOutlined, InboxOutlined } from "@ant-design/icons";
import { Button, Card, Form, Rate, Row, Upload } from "antd";
import TextArea from "antd/lib/input/TextArea";
import { Router, useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import useInput from "../hooks/useInput";
import { ADD_COMMENT_REQUEST, REMOVE_IMAGE, UPLOAD_IMAGES_REQUEST } from "../reducers/post";
const NewComment = ({post}) => {
const dispatch = useDispatch();
const router = useRouter();
const [fileList, setFileList] = useState([]);
const id = useSelector((state) => state.user.me?.id);
const {imagePaths, addCommentDone, addCommentLoading} = useSelector((state) => state.post);
const [value, setValue] = useState({});
const [ content, onChangeContent, setContent ] = useInput('');
//별점 체크
const handleRate = (value) => {
setValue(value);
console.log("점수:::" + value);
};
const onChangeImages = useCallback(({ fileList: newFileList }) => {
setFileList(newFileList);
console.log('images', fileList);
const imageFormData = new FormData();
console.log("파일리스트 길이", fileList.length);
for(let i = 0; i< fileList.length; i++){
imageFormData.append('image', fileList[i].originFileObj);
console.log("파일리스트 키?", fileList[i].originFileObj);
}
return dispatch({
type: UPLOAD_IMAGES_REQUEST,
data: imageFormData
});
});
//미리보기
const onPreview = async (file) => {
let src = file.url;
if (!src) {
src = await new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () => resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow?.document.write(image.outerHTML);
};
//댓글 등록
const onSubmit = useCallback(({ fileList: newFileList }) => {
const formData = new FormData();
setFileList(newFileList);
for(let i = 0; i< fileList.length; i++){
formData.append('image', fileList[i].originFileObj);
}
formData.append('content', content);
formData.append('rate', value);
formData.append('postId', post.id);
formData.append('userId', id);
dispatch({
type: ADD_COMMENT_REQUEST,
data: formData
});
}, [content, value, imagePaths]);
const onRemoveImage = useCallback((index) => () => {
dispatch({
type: REMOVE_IMAGE,
data: index
});
});
return(
<div>
<Form encType="multipart/form-data" onFinish={onSubmit}>
<Card
style={{
width: 400,
marginLeft: 720, height: 0
}}
>
<Row><h2>Comment</h2></Row>
<Row><h2>My rate is...</h2></Row>
<Row><Rate style={{fontSize: 50}} onChange={handleRate} value={value} /></Row>
<br />
<Row><h2>My commnet is...</h2></Row>
<Row> <TextArea
showCount
maxLength={250}
value={content}
onChange={onChangeContent}
style={{
height: 110,
resize: 'none',
}}
/>
</Row>
<br />
<Row><h2>I recommend this!</h2></Row>
<Row><h3>아이템 추천은 2개까지 가능해요.</h3></Row>
<Row>
<Upload
name='image'
listType="picture-card"
multiple
fileList={fileList}
onChange={onChangeImages}
onPreview={onPreview}
onRemove={onRemoveImage}
>
{ fileList.length < 2 && 'Drag images or click' }
</Upload>
</Row>
<Button htmlType="submit">댓글 달기</Button>
</Card>
</Form>
</div>
);
};
export default NewComment;
엇 저 newFilest가 antd문서에서 저렇게 해야한다고 해서 했는데 저 부분이 문제인가요? 말씀해주신 onChnageImages(1번), onSubmit(2번) 함수에 콘솔을
const onSubmit = useCallback(({ fileList: newFileList }) => {
const formData = new FormData();
console.log("댓글 등록newFileList: ", newFileList);
setFileList(newFileList);
const onChangeImages = useCallback(({ fileList: newFileList }) => {
console.log("이미지 등록newFileList: ", newFileList);
setFileList(newFileList);
위와 같이찍었을 때
1번은 이렇게
2번은
이렇게 나옵니다
아 선생님선생님 보니까 onChnageImages 부분에서 이미 newFileList를 정의해서 setFileList에 담았으니까
onSumit에서는
setFileList(fileList);
이렇게만 담아주니까(먼저 실행한 함수(onChnageImages)에서 담아서 fileList에 이미 데이터가 담겨있다고 생각해서)
에러가 해결됏ㄴ는데 이렇게 하느거 맞나요?
네. 아니면 newFilelist가 undefined가 아닐 때만 하면 set하면 되죠.
이렇게 값이 undefined인데는 이유가 다 있는 겁니다. 그걸 거꾸로 찾는 연습을 많이 하세요.
originFileObj 속성이 File이니 그걸 append 해보세요.