[정상인] sprint7#117
Hidden character warning
Conversation
|
스프리트 미션 하시느라 수고 많으셨어요. |
|
역시 상인님... 질문을 '읽는 사람'을 고려하여 정말 잘 전달주셨네요. 커뮤니케이션 스킬이 남다르십니다. 🥺 URL parameter 를 가져오고 각 컴포넌트의 props로 넘겨주는 것과, 각 컴포넌트들에서 URL parameter를 가져오는 것에 대해서 고민을 많이했습니다. 의견이 궁금합니다.제 생각에서는 컴포넌트가 다른 출처에서 받은 |
(질문) 태그 인풋은 엔터를 누를 때 등록되게 해서 따로 form을 구성했는데 이 방법이 어색한 느낌이 들어서 어떤지 궁금합니다.헐... 죄송합니다 제가 코드를 잘못 읽고 질문에 대한 답변을 잘 못했네요 ㅠㅠㅠ HTML 표준 위반이 아니며, 편하고 유용한 방법일 것으로 사료되기는 하나 일반적으로 하나의 제출 단위로 필요한 다음과 같이 해볼 수도 있겠네요. <form className="addItem-form" onSubmit={handleSubmitAddItem}>
<fieldset>
<legend className="sr-only">상품 기본 정보</legend>
<AddItemFormHeader formData={formData} />
<AddItemImage ... />
<AddItemName value={formData.name} onChange={(e)=>handleChange(e,"name")} />
<AddItemDescription ... />
<AddItemPrice ... />
</fieldset>
<fieldset>
<AddItemTag
value={inputValueTag}
onChange={(e) => setInputValueTag(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleSubmitTag();
}
}}
/>
<button type="button" onClick={handleSubmitTag}>태그 추가</button>
</fieldset>
<div className="actions">
<button type="submit">상품 등록</button>
</div>
</form>다만, '일반적' 이라는 말이 참 모호하죠. 하핳.. 지금 상인님께서 작성하신 코드는 그렇다고 해서 HTML의 표준을 위배하고 있지는 않기에 그대로 두셔도 무방할 것으로 보입니다. |
| DropDown.header = ({ children }) => { | ||
| return <div>{children}</div>; |
There was a problem hiding this comment.
크으.. 상인님 코드를 볼 때마다 느끼는거지만 유지보수에 신경을 많이 쓰시는 것 같아요.
그냥 Dorpdown children에 <div>로처리하셨을 수도 있었을텐데, 드롭다운이 공통적으로 변경되는 것도 고려하여 이렇게 설계하신게 느껴집니다.. 👍
| const ScrollToTop = () => { | ||
| const { pathname } = useLocation(); | ||
|
|
||
| useEffect(() => { | ||
| window.scrollTo(0, 0); | ||
| }, [pathname]); | ||
|
|
||
| return null; | ||
| }; |
There was a problem hiding this comment.
오잉? 요건 그냥 훅으로 만들어도 되겠는데요?
| const ScrollToTop = () => { | |
| const { pathname } = useLocation(); | |
| useEffect(() => { | |
| window.scrollTo(0, 0); | |
| }, [pathname]); | |
| return null; | |
| }; | |
| const useFixTop = () => { | |
| const { pathname } = useLocation(); | |
| useEffect(() => { | |
| window.scrollTo(0, 0); | |
| }, [pathname]); | |
| }; |
컴포넌트는 보통 실체하는 ui가 있을 때 사용되는건데 이건 컴포넌트로 보긴 어렵겠네요.
| <TagBadge name={tag} onDelete={() => onDelete(index)} /> | ||
| <TagBadge | ||
| name={tag} | ||
| onDelete={onDelete ? () => onDelete(index) : undefined} |
There was a problem hiding this comment.
해당 코드는 다음과 같이 함축할 수 있겠네요 !
| onDelete={onDelete ? () => onDelete(index) : undefined} | |
| onDelete={onDelete && () => onDelete(index)} |
어떻게 이게 가능할까요?
- 자바스크립트에서
&&연산자는 앞의 값이 truthy일 때만 뒤의 값을 평가합니다. - 즉,
onDelete가 존재하지 않으면false가 반환되어onDeleteprops에는false가 들어갑니다. - React는
false,null,undefined를 렌더링하지 않는 값으로 처리하므로, 결과적으로onDelete가 전달되지 않은 것과 동일하게 동작합니다.
| export const QUESTION_PLACEHOLDER = | ||
| "개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다."; |
There was a problem hiding this comment.
해당 상수는 파일을 분리함으로써 어떤 이득을 취할 수 있을까요? 🤔
사용되는 컴포넌트와 멀리 떨어져 있기에 개발 경험이 떨어질 수 있으며 유지보수를 위하기에는 재사용될 가능성이 낮아보일 것으로 사료되는군요 ..!
다국어를 지원하는 경우 텍스트들을 중앙집중형으로 관리하기도 하나, 지금은 다른 케이스인 것으로 보이는군요 😉
| export const useGetProductCommentsQuery = ({ productId, limit, cursor }) => { | ||
| return useQuery({ | ||
| queryKey: ["getProductComments", productId, limit], | ||
| queryFn: () => getProductComments({ productId, limit, cursor }), | ||
| staleTime: 300000, | ||
| select: (response) => response.data, | ||
| }); |
There was a problem hiding this comment.
크으.. 기초 프로젝트 때 익히신 react-query를 사용해보셨군요 ! 😊
배우신 것을 바로 응용해보시는 상인님의 학습 열정 리스펙합니다 👍👍
|
|
||
| export const useGetProductCommentsQuery = ({ productId, limit, cursor }) => { | ||
| return useQuery({ | ||
| queryKey: ["getProductComments", productId, limit], |
There was a problem hiding this comment.
그런데, cursor도 함께 쿼리키에 포함하여야 하지 않을까요?
| queryKey: ["getProductComments", productId, limit], | |
| queryKey: ["getProductComments", productId, limit, cursor], |
두 번째 커서, 세 번째 커서, 각기 다른 결과값을 가져올 것으로 보여서요 !
| const { data, isLoading, isError, error } = useGetProductCommentsQuery({ | ||
| productId, | ||
| limit: 3, | ||
| }); | ||
|
|
||
| if (isLoading) { | ||
| return <LoadingSpinner />; | ||
| } | ||
|
|
||
| if (isError) { | ||
| return <ErrorMessage errorMessage={error.message} />; | ||
| } | ||
|
|
||
| const comments = data.list; |
There was a problem hiding this comment.
다음과 같이 구조분해 할당을 통해 간결하게 표현해볼 수도 있습니다 !
| const { data, isLoading, isError, error } = useGetProductCommentsQuery({ | |
| productId, | |
| limit: 3, | |
| }); | |
| if (isLoading) { | |
| return <LoadingSpinner />; | |
| } | |
| if (isError) { | |
| return <ErrorMessage errorMessage={error.message} />; | |
| } | |
| const comments = data.list; | |
| const { data: { | |
| list: comments | |
| }, isLoading, isError, error } = useGetProductCommentsQuery({ | |
| productId, | |
| limit: 3, | |
| }); | |
| if (isLoading) { | |
| return <LoadingSpinner />; | |
| } | |
| if (isError) { | |
| return <ErrorMessage errorMessage={error.message} />; | |
| } |
const comments = data.list;를 따로 선언하지 않고 받아올 때 구조분해 할당으로 선언과 동시에 별칭도 붙일 수 있어요 😉
| const { | ||
| data: productInfo, | ||
| isLoading, | ||
| isError, | ||
| error, | ||
| } = useGetProductDetailQuery({ | ||
| productId, | ||
| }); | ||
|
|
||
| if (isLoading) return <LoadingSpinner />; | ||
|
|
||
| if (isError) return <ErrorMessage errorMessage={error.message} />; |
There was a problem hiding this comment.
아으.. 너무 깔끔합니다 !
loading, error 처리. 리액트 쿼리의 기본적인 상태들을 사용하시니까 코드가 훨씬 간결해졌네요 👍
|
크으 ~ 너무 좋습니다 상인님. |
요구사항
배포링크
기본
심화
주요 변경사항
스크린샷
멘토에게
변경 전
변경 후 : 각 컴포넌트에서 직접 가져옵니다.