-
Notifications
You must be signed in to change notification settings - Fork 39
[박다인] sprint6 #202
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
The head ref may contain hidden characters: "React-\uBC15\uB2E4\uC778-sprint6"
[박다인] sprint6 #202
Changes from all commits
b2e37bd
6f8bbb0
e11e25f
212e864
4dc5dd0
f568de3
e202fe8
32395de
a2b32ef
762bcb6
e08d232
42384a1
52fa9db
68128e9
5a993ed
07a4ea8
0ae353e
52a0b57
b6f844b
c3b6c62
223a60a
ad6561a
96d75dc
5264dba
3e6d3a3
2a27d68
ad7f921
5dfd895
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| import { useRef, useState } from "react"; | ||
| import "./additem.css"; | ||
| import { postProducts } from "../../api"; | ||
| import plus from "../../assets/plus.png"; | ||
| import xbtn from "../../assets/X.png"; | ||
|
|
||
| function AddItemPage() { | ||
| const [newItem, setNewItem] = useState({ | ||
| images: null, | ||
| tagInput: "", | ||
| tags: [], | ||
| price: "", | ||
| description: "", | ||
| name: "", | ||
| }); | ||
|
|
||
| const [imageAlert, setImageAlert] = useState(""); | ||
| const fileInputRef = useRef(null); | ||
|
|
||
| const handleInputChange = (field, value) => { | ||
| setNewItem((prev) => ({ ...prev, [field]: value })); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. React의 Batch 업데이트 특성을 생각해서 이렇게 처리하신건가요? 굳굳!! 👍 |
||
| }; | ||
|
|
||
| const handleTagKeyDown = (e) => { | ||
| if (e.key === "Enter" && newItem.tagInput.trim() !== "") { | ||
| e.preventDefault(); | ||
| const newTag = newItem.tagInput.trim(); | ||
| if (!newItem.tags.includes(newTag)) { | ||
| setNewItem((prev) => ({ | ||
| ...prev, | ||
| tags: [...prev.tags, newTag], | ||
| tagInput: "", | ||
| })); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const handleRemoveTag = (tagToRemove) => { | ||
| setNewItem((prev) => ({ | ||
| ...prev, | ||
| tags: prev.tags.filter((tag) => tag !== tagToRemove), | ||
| })); | ||
| }; | ||
|
|
||
| const handleImageClick = () => { | ||
| if (newItem.images) { | ||
| setImageAlert(`*이미지 등록은 최대 1개까지 가능합니다.`); | ||
| return; | ||
| } | ||
| fileInputRef.current.click(); | ||
| }; | ||
|
|
||
| const handleCreateItem = async () => { | ||
| const itemData = { | ||
| images: newItem.images, | ||
| tags: newItem.tags, | ||
| price: newItem.price, | ||
| description: newItem.description, | ||
| name: newItem.name, | ||
| }; | ||
|
|
||
| const result = await postProducts(itemData); | ||
|
|
||
| if (result) { | ||
| setNewItem({ | ||
| images: null, | ||
| tags: [], | ||
| price: "", | ||
| description: "", | ||
| name: "", | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| const isFormValid = | ||
| newItem.name.trim() !== "" && | ||
| newItem.description.trim() !== "" && | ||
| newItem.price !== "" && | ||
| newItem.tags.length > 0; | ||
|
|
||
| return ( | ||
| <> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기 fragment가 필요없습니다 :) |
||
| <div className="item__container"> | ||
| <div className="item__register"> | ||
| <h1>상품 등록하기</h1> | ||
| <button | ||
| className={`item__register--button ${isFormValid ? "active" : ""} `} | ||
| disabled={!isFormValid} | ||
| onClick={handleCreateItem} | ||
| > | ||
| 등록 | ||
| </button> | ||
| </div> | ||
| <div className="item__info"> | ||
| <div className="item__image"> | ||
| <h2>상품 이미지</h2> | ||
| <div className="item__image-wrapper"> | ||
| <button | ||
| type="button" | ||
| onClick={handleImageClick} | ||
| className="item__image-upload" | ||
| > | ||
| <img className="item__image-plus" src={plus} alt="추가" /> | ||
| <span className="upload-text">이미지 등록</span> | ||
| <input | ||
| type="file" | ||
| ref={fileInputRef} | ||
| accept="image/*" | ||
| style={{ display: "none" }} | ||
| onChange={(e) => { | ||
| if (newItem.images) { | ||
| setImageAlert(`*이미지 등록은 최대 1개까지 가능합니다.`); | ||
| return; | ||
| } | ||
| handleInputChange("images", e.target.files[0]); | ||
| setImageAlert(""); // 정상 등록이면 에러 제거 | ||
| }} | ||
| /> | ||
|
Comment on lines
+110
to
+118
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인라인으로 이렇게 작성해주는것보다는 핸들러를 따로 만들어주어 리턴문을 최대한 깔끔히 작성해주는게 좋아요. 그리고 해당 코드블락이 중복되는것같은데 공통 함수로 추출해서 중복을 제거해줄까요? const validateImageUpload = () => {
if (newItem.images) {
setImageAlert(`*이미지 등록은 최대 1개까지 가능합니다.`);
return;
}
} |
||
| </button> | ||
| {newItem.images && ( | ||
| <div className="item__image-preview"> | ||
| <img | ||
| src={URL.createObjectURL(newItem.images)} | ||
| alt="preview" | ||
| /> | ||
| <button | ||
| className="item__delete" | ||
| onClick={() => { | ||
| handleInputChange("images", null); | ||
| setImageAlert(""); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러 상태또한, 아래와 같이 이미지 에러에 관련된 상태 메시지를 공유하지않고 필드마다 각각 다른 에러 상태를 가질수있게끔 개선해보면 좋을것같아요. const [errors, setErrors] = useState({
image: "",
name: "",
description: "",
price: "",
tags: ""
}); |
||
| }} | ||
| > | ||
| <img src={xbtn} alt="삭제" /> | ||
| </button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| {imageAlert && <p className="item__alert">{imageAlert}</p>} | ||
| </div> | ||
| <div className="item__name"> | ||
| <h2>상품명</h2> | ||
| <input | ||
| type="text" | ||
| value={newItem.name} | ||
| onChange={(e) => handleInputChange("name", e.target.value)} | ||
| className="item__input" | ||
| placeholder="상품명을 입력해주세요" | ||
| /> | ||
| </div> | ||
| <div className="item__description"> | ||
| <h2>상품 소개</h2> | ||
| <textarea | ||
| type="text" | ||
| value={newItem.description} | ||
| onChange={(e) => handleInputChange("description", e.target.value)} | ||
| className="item__textarea" | ||
| placeholder="상품 소개를 입력해주세요" | ||
| /> | ||
| </div> | ||
| <div className="item__price"> | ||
| <h2>판매 가격</h2> | ||
| <input | ||
| type="number" | ||
| value={newItem.price} | ||
| onChange={(e) => handleInputChange("price", e.target.value)} | ||
| className="item__input" | ||
| placeholder="판매 가격을 입력해주세요" | ||
| /> | ||
| </div> | ||
| <div className="item__tags"> | ||
| <h2>태그</h2> | ||
| <div className="item__tag"> | ||
| <input | ||
| type="text" | ||
| value={newItem.tagInput} | ||
| onChange={(e) => handleInputChange("tagInput", e.target.value)} | ||
| onKeyDown={handleTagKeyDown} | ||
| className="item__input" | ||
| placeholder="태그를 입력해주세요" | ||
| /> | ||
| <div className="item__tag-list"> | ||
| {newItem.tags.map((tag) => ( | ||
| <div key={tag} className="item__tag-hash"> | ||
| <span>#{tag}</span> | ||
| <button | ||
| className="item__delete" | ||
| onClick={() => handleRemoveTag(tag)} | ||
| > | ||
| <img src={xbtn} alt="삭제" /> | ||
| </button> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default AddItemPage; | ||
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.
form state를 다루고있네요!
상태 객체가 너무 큰 묶음으로 관리되다보면 불필요한 리렌더링이 자주 발생될거예요.
아래와 같이 관련된 상태끼리 묶어서 분리해볼까요?