-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[이재완] Sprint7 #217
The head ref may contain hidden characters: "React-\uC774\uC7AC\uC644-sprint7"
[이재완] Sprint7 #217
Conversation
…-sprint8 [이재완] Sprint1
…-sprint2 [이재완]Sprint2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그동안 충돌도 많이 났고, 고생하셨는데 해결 하시느라 수고하셨습니다! 🙏
function ImageUpload({ title }) { | ||
const [imagePreviewUrl, setImagePreviewUrl] = useState(""); | ||
|
||
const handleImageChange = (event) => { | ||
const file = event.target.files[0]; | ||
if (file) { | ||
// 미리보기 주소 값(Object URL) 생성 | ||
const imageUrl = URL.createObjectURL(file); | ||
setImagePreviewUrl(imageUrl); | ||
} | ||
}; | ||
|
||
const handleDelete = () => { | ||
setImagePreviewUrl(""); // 미리보기 URL 리셋 | ||
}; | ||
|
||
return ( | ||
<div> | ||
{title && <Label>{title}</Label>} | ||
|
||
<ImageUploadContainer> | ||
{/* HiddenFileInput의 id와 label의 htmlFor 값을 매칭해 주세요 */} | ||
<UploadButton htmlFor="image-upload"> | ||
<PlusIcon /> | ||
이미지 등록 | ||
</UploadButton> | ||
|
||
<HiddenFileInput | ||
id="image-upload" | ||
type="file" | ||
onChange={handleImageChange} | ||
accept="image/*" // 이미지 파일만 업로드 가능하도록 제한 | ||
/> | ||
|
||
{/* 업로드된 이미지가 있으면 썸네일 렌더링 */} | ||
{imagePreviewUrl && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 파일처럼 스타일링 관련된 컴포넌트가 많아서, 중요한 컴포넌트에 관심을 가지기 어려운 상태가 있을 수 있어요.
그렇다면, 파일을 나누는 방법이 있어요.
ㄴ src
ㄴ UI
ㄴ ImageUpload
ㄴ ImageUpload.jsx
ㄴ ImageUpload.styled.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그리고 styled-components는 전부 ImageUpload.styled.js 선언 후, ImageUpload 컴포넌트에서 import 해서 사용하는 방법도 현업에서 자주 사용되는 패턴이예요.
useEffect(() => { | ||
const handleResize = () => { | ||
setPageSize(getPageSize()); | ||
}; | ||
|
||
// 화면 크기 변경할 때마다 pageSize를 다시 계산해 넣음 | ||
window.addEventListener("resize", handleResize); | ||
fetchSortedData({ orderBy, page, pageSize }); | ||
|
||
// Cleanup function | ||
return () => { | ||
window.removeEventListener("resize", handleResize); | ||
}; | ||
}, [orderBy, page, pageSize]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 아래처럼 리사이즈를 감지하는 부분을 별도의 커스텀 훅으로 빼면, 다음번에 재사용할 수 있어요.
리사이즈 되었을 때 실행할 함수를 콜백으로 넘겨주는거에요.
const useWindowResize = (onResize) => {
useEffect(() => {
const handleResize = () => {
onResize();
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [onResize]);
};
export async function getProducts(params = {}) { | ||
// URLSearchParams을 이용하면 파라미터 값을 자동으로 쉽게 인코딩할 수 있어요. | ||
const query = new URLSearchParams(params).toString(); | ||
|
||
try { | ||
const response = await fetch( | ||
`https://panda-market-api.vercel.app/products?${query}` | ||
); | ||
if (!response.ok) { | ||
throw new Error(`HTTP error: ${response.status}`); | ||
} | ||
const body = await response.json(); | ||
return body; | ||
} catch (error) { | ||
console.error("Failed to fetch products:", error); | ||
throw error; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이게 API 요청이 여러개 될때 https://panda-market-api.vercel.app
값은 반복될 수 있어요.
해당 파일 상위에
const API_BASE_URL = 'https://panda-market-api.vercel.app/';
선언해서 공통 일괄 사용하는 방법이 좋겠습니다.
function ImageUpload({ title }) { | ||
const [imagePreviewUrl, setImagePreviewUrl] = useState(""); | ||
|
||
const handleImageChange = (event) => { | ||
const file = event.target.files[0]; | ||
if (file) { | ||
// 미리보기 주소 값(Object URL) 생성 | ||
const imageUrl = URL.createObjectURL(file); | ||
setImagePreviewUrl(imageUrl); | ||
} | ||
}; | ||
|
||
const handleDelete = () => { | ||
setImagePreviewUrl(""); // 미리보기 URL 리셋 | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 부분에 아래처럼 컴포넌트가 언마운트될 때 예외처리가 추가 되면 좋겠습니다.
아래에서 사용한 URL.createObjectURL통해 생성된 URL은 브라우저 메모리에 객체를 유지해요. 즉, 사용하지 않을 땐 메모리에서 이를 해제해야 합니다. 페이지를 새로고침하거나 닫으면 자동으로 브라우저에서 해제를 하는데, 우리가 사용중인 React(SPA) 에서는 직접 해제를 해줘야해요. 닫히거나 새로고침이 안되기 때문이죠.
useEffect(() => {
return () => {
if (imagePreviewUrl) {
URL.revokeObjectURL(imagePreviewUrl);
}
};
}, [imagePreviewUrl]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 하게 되면 메모리 누수도 방지하고, 코드의 효율성도 향상시킬 수 있어요.
/> | ||
|
||
{/* 업로드된 이미지가 있으면 썸네일 렌더링 */} | ||
{imagePreviewUrl && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기서 imagePreviewUrl 값이 boolean 값이 아니라면 문제가 발생할 수 있어요.
예를들어, value 값이 0이라면 UI에 0이 표시될 수 있고, undefined 라면 에러가 발생할 수 있어요.
아래 포스트를 한번 읽어봐주세요.
위 케이스를 방지하기 위해 value 값이 정확하지 않다면 아래처럼 사용하는걸 추천드려요.
{imagePreviewUrl && (
...
): null }
충돌 해결 중 .gitignore 파일이 누락되서 제가 추가했어요! 꼭 잊지말아주세요~ 💪
|
요구사항
기본
상품 상세
=> favoriteCount : 하트 개수
=> images : 상품 이미지
=> tags : 상품태그
=> name : 상품 이름
=> description : 상품 설명
상품 문의 댓글
=> image : 작성자 이미지
=> nickname : 작성자 닉네임
=> content : 작성자가 남긴 문구
=> description : 상품 설명
=> updatedAt : 문의글 마지막 업데이트 시간
주요 변경사항
스크린샷
멘토에게