-
Notifications
You must be signed in to change notification settings - Fork 2
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
[FE] feat: 리뷰 목록 페이지에 변경된 API 연동 후, 리액트 쿼리 적용 및 무한 스크롤 구현 #164
Conversation
- ReviewPreviewList 인터페이스 추가 - size, lastReviewId, reviews 필드 추가 - ReviewPreview 인터페이스 수정 - keywords 필드를 Keyword[] 타입으로 변경
- 기존의 단순 반환 핸들러에서 페이지네이션 기능을 포함한 핸들러로 수정
* feat: PR Request 생성 / Comment 시 디스코드 멘션 * fix: Case-sensitive ID로 해결, ALL webhook 추가 * fix: remove whitespace * chore: rename workflow * fix: fix shell script * fix: step statement * fix: remove trailing whitespace after equals sign
* feat: 랜덤한 문자열 생성기 구현 Co-authored-by: donghoony <aru0504@naver.com> * feat: 리뷰 그룹 생성 요청, 응답 형식 Co-authored-by: donghoony <aru0504@naver.com> * feat: 리뷰 그룹 생성 Co-authored-by: hyeonjilee <skylar12200@gmail.com> Co-authored-by: nayonsoso <panda0329@naver.com> * feat: 리뷰 작성 컨트롤러 Co-authored-by: hyeonjilee <skylar12200@gmail.com> Co-authored-by: nayonsoso <panda0329@naver.com> --------- Co-authored-by: nayonsoso <panda0329@naver.com> Co-authored-by: hyeonjilee <skylar12200@gmail.com>
* refactor: 불필요한 update 방지 * feat: controller에 리뷰 생성 요청에 대한 응답 기능 구현 * feat: controller에 리뷰 작성을 위해 필요한 정보를 응답 기능 구현 * feat: service에 리뷰 작성을 위해 필요한 정보 조회 기능 구현 * chore: 사용하지 않는 dto 삭제 * test: 리뷰 리뷰 작성을 위해 필요한 정보 조회 기능 테스트 작성 * refactor: swagger 설정 일부 수정 및 에러 응답코드 노출되도록 변경 * refactor: service에서 원시타입 long을 반환하도록 변경 * docs: api 문서 dto 항목설명 변경 * test: 사용하지 않는 변수 선언하지 않도록 변경 * docs: api 문서 검증 내용에 대한 어체 변경 * refactor: 트랜잭션 조회 명시 추가 * refactor: dto 이름 변경
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.
일단 무스를 끝낸 것에 박수를 보내요.👏👏👏👏👏👏
쑤쑤 리뷰 남겼어요.
return ( | ||
<S.Layout> | ||
<S.Layout data-id={id}> |
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.
data-id를 사용한 이유가 있을까요?
data-id가 개발자 도구를 켰을때 보이는 거라 서버에서 받은 데이터가 개발자 도구에 바로 보일 거라 보안상 우려가되네요.
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.
아하.. 보안 이슈도 있군요..
에프이에게 비슷한 질문을 받아서 해당 답변을 읽어보면 될 것 같아요!
@@ -1,40 +1,25 @@ | |||
import GithubLogo from '@/assets/githubLogo.svg'; | |||
import Lock from '@/assets/lock.svg'; |
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.
svg를 Icon을 뒤에 붙여주세요.
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.
또또또 까먹었네요....😭
<img src={isPublic ? UnLock : Lock} alt="자물쇠 아이콘" /> | ||
<span>{isPublic ? '공개' : '비공개'}</span> |
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.
alt가 리뷰 공개/비공개에 따라 달라지면 좋겠어요.
리뷰 공개/비공개에 따른 src,alt, span의 text를 객체로 만들어서 관리하면 좋을것 같아요.
@@ -11,10 +11,10 @@ export const Layout = styled.div` | |||
|
|||
&:hover { | |||
cursor: pointer; | |||
border: 1px solid ${({ theme }) => theme.colors.primaryHover}; | |||
border: 1px solid ${({ theme }) => theme.colors.lightPurple}; |
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.
'px' 또 찾았다!!
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.
전체검색에서 px
찾아서 다 고쳤습니다~~~
export const useReviewPreviewList = () => { | ||
const { data, fetchNextPage, hasNextPage, isLoading, error } = useInfiniteQuery({ | ||
queryKey: [QUERY_KEYS.reviews], | ||
queryFn: ({ pageParam = 0 }) => |
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.
pageParam이 무엇일까요?
pageParam이 lastReviewId로 사용되는데, 제가 알고 있기로는 서버에서 lastReviewId를 넘겨주지 않으면 빈값이여야해요. 그런데 pageParam의 기본값이 0이라서 서버에서 lastReviewId가 없을 때 빈값이 아닌 0이 lastReviewId 값으로 들어가게 돼요. 그렇다면 실제 서버와 api연동 시 리뷰 목록 불러오기에서 오류가 날것 같아요.
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.
pageParam이 카드 목록 중 마지막 카드 아이템 id예요. 그래서 pageParam 값을 lastReviewId에 넣었어요.
생각해보니 pageParam이 0이 되면 안되는데 0값을 계속 넣어주고 있었네요ㅠㅠ 고마워요!
const limit = isFirstPage ? PAGE.defaultPageSize : PAGE.additionalPageSize; | ||
const startIndex = isFirstPage | ||
? PAGE.firstPageStartIndex | ||
: REVIEW_PREVIEW_LIST.reviews.findIndex((review) => review.id === lastReviewId) + 1; |
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.
1이라는 매직넘버 발견!
코드가 많아서 상수를 분리하는 것을 놓칠 수 있죠
이렇게 놓치는 것을 잡아주는데 코드 리뷰의 순기능일지도?
😊!!
{buttons.map((button, index) => ( | ||
<S.ButtonContainer key={index}> | ||
{buttons.map((button) => ( | ||
<> |
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.
쑤쑤 ErrorSection의 변경 사항 제꺼에 반영했어요.
ButtonContainer가 삭제되면서 flagment 가 생겼는데 이유는 뭘까요?
{isLoading && <LoadingPage />} | ||
{error && <p>{error.message}</p>} |
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.
loading,error 에 Suspense, ErrorBoundary를 아직 적용이 안되는 건가요?
error시에도 error.message를 띄워주니 ErrorSection처럼 피그마에 적용한 오류 시안을 ui로 적용하고 있지않네요.
|
||
const lastReviewElementRef = useCallback( | ||
(node: HTMLElement | null) => { | ||
if (isLoading) return console.log('isLoading', isLoading); |
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.
질문1: console.log는 무한 스크롤을 확인하기 용도인가요? 그렇다면 push에서는 console.log를 지워주어야 하지 않나요?
질문2: 무한 스크롤로 데이터를 불러오는 동안, lastReviewElementRef에서 로딩 중임을 보여주는 UI가 필요해요.
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.
질문1 답변 : 무한스크롤 확인하려고 넣었던 코드인데 지우질 않았네요😅
질문2 답변 : 그 부분을 제가 놓치고 있었네요. LoadingBar
컴포넌트를 넣을게요!
frontend/src/types/review.ts
Outdated
@@ -60,6 +60,12 @@ export interface WritingReviewInfoData { | |||
keywords: Keyword[]; | |||
} | |||
|
|||
export interface ReviewPreviewList { | |||
size: number; | |||
lastReviewId: number; |
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.
lastReviewId은 타입도 number로 되어있어서 이거를 수정해야겠네요.
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.
쑤쑤 우선 무스 기능 구현을 성공한 것에 박수를 보내요.👏👏👏👏👏👏👏👏 역시 쑤쑤가 해낼 줄 알았지
쑤쑤의 성장을 바라며 코멘트를 남겨뒀어요. 확인 부탁드려요.
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.
미션 할 때 무한 스크롤 때문에 고생했던 기억이 눈에 선하네요🥲 정말 고생 많았어요
useInfiniteQuery
를 사용한 부분은 함께 이야기 나눈 부분이라 별도로 피드백을 남기진 않았어요. 코멘트 확인 부탁해요!
@@ -11,10 +11,10 @@ export const Layout = styled.div` | |||
|
|||
&:hover { | |||
cursor: pointer; | |||
border: 1px solid ${({ theme }) => theme.colors.primaryHover}; | |||
border: 1px solid ${({ theme }) => theme.colors.lightPurple}; |
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.
rem 단위로 통일시키면 좋을 것 같아요👀
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.
아이고~~~~ 또 px
을 썼네요😭
다시는 이런 실수를 반복하지 않겠습니다!!!!!!
{buttons.map((button) => ( | ||
<> | ||
<Button | ||
buttonType={button.buttonType} | ||
text={button.text} | ||
image={button.image} | ||
imageDescription={button.imageDescription} | ||
onClick={button.onClick} | ||
/> | ||
</S.ButtonContainer> | ||
</> |
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.
<S.ButtonContainer> 내부에서 map
을 사용하고, Button
컴포넌트에 key를 추가하는 것은 어떨까요?
<S.Container>
<S.ButtonContainer>
{buttons.map((button) => (
<Button
// ...
/>
)}
</S.ButtonContainer>
</S.Container>
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.
아하 key
를 추가하는 게 좋겠네요!
<S.ButtonContainer>
는 불필요해서 제거했고 <S.Container>
안에서 map
을 돌리고 Button
에 key
를 추가했어요!
<S.Container>
{buttons.map((button, index) => (
<>
<Button
key={index}
buttonType={button.buttonType}
text={button.text}
image={button.image}
imageDescription={button.imageDescription}
onClick={button.onClick}
/>
</>
))}
</S.Container>
return ( | ||
<S.Layout> | ||
<S.Layout data-id={id}> |
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.
data attribute를 사용한 이유가 궁금해요!
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.
export interface ReviewPreview {
id: number;
isPublic: boolean;
reviewerGroup: {
id: number;
name: string;
thumbnailUrl: string;
};
createdAt: string;
contentPreview: string;
keywords: Keyword[];
}
ReviewPreviewCard
컴포넌트에는 원래 reviewerGroup, createdAt, contentPreview, keywords, isPublic
만 필요했지만, 새로운 인터페이스를 정의하지 않고 기존의 ReviewPreview
인터페이스를 재사용하기 위해 id
를 추가했어요. id
를 사용할 자리가 없어 data-id
속성을 활용했어요.
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.
data-id={id}
서버에서 받아온 리뷰의 id를 data-id에 써서, html에서 리뷰 id가 노출될 것 같아요.
data-id가 어디에서 사용되고 있는건가요?
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.
ReviewPreviewCard
컴포넌트에서 사용되고 있어요. id 제거할게요!
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.
무한 스크롤 끝까지 붙잡고 있던 쑤쑤에게 박수~
크리티컬한 건 이미 바다가 잡아줘서 저는 비교적 간단한 코멘트만 남기고 어프루브 할게요!
`; | ||
|
||
export const HeaderContainer = styled.div` |
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.
Container에서 Content로 이름을 바꾼 이유가 궁금합니다!
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.
Layout
안에 Header, Main
을 기준으로 Header
안에 HeaderContainer
가 있는 게 어색한 것 같아서 HeaderContent
로 바꿨어요!
<S.Layout>
<S.Header>
<S.HeaderContent />
</S.Header>
<S.Main />
</S.Layout>
defaultPageSize: 5, | ||
additionalPageSize: 2, | ||
}; | ||
|
||
const getDetailedReview = () => | ||
http.get(endPoint.gettingDetailedReview(123456, 123456), async ({ request }) => { |
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.
이렇게 123456을 사용하는 부분도 가능하다면 상수화나 간단한 주석이 있으면 나중에 알아보기 더 편할 것 같아요!
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 getReviewPreviewList = () => | ||
http.get(endPoint.gettingReviewList(1, 3, 1), async ({ request }) => { | ||
return HttpResponse.json(REVIEW_PREVIEW_LIST); | ||
const getReviewPreviewList = () => { |
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.
이렇게 디테일하게 get 핸들러 함수를 작성한 이유는 무한 스크롤 로직(API가 보내주는 데이터를 프론트에서 의도한 대로 처리하고 있는지)이 정상적으로 작동하는지 확인하기 위함이었나요?
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.
네 맞아요. 서버에는 리뷰 목록 개수가 많지 않아서 무한스크롤을 제대로 확인하기 힘들어서 get 핸들러 함수에 디폴트 페이지, 추가적으로 불러오는 페이지 개수 등을 구체적으로 명시해서 제대로 기능이 작동하는지 확인했어요
if (isLoading) return console.log('isLoading', isLoading); | ||
if (observer.current) observer.current.disconnect(); | ||
|
||
observer.current = new IntersectionObserver((entries) => { |
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.
지금은 바빠서 무리겠지만 ㅎㅎ;
IntersectionObserver
를 이용한 무한 스크롤 로직은 별도의 훅으로 분리해보면 좋을 것 같아요.
제가 예전에 무한스크롤 훅에 대한 글을 썼었는데 참고해보세용~~ 이거
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.
추후 리팩할 부분 중 하나였는데 짚어주셔서 너무 감사해요!!!
참고자료까지 너무 고마워요 올리❤️
* fix: src/index.tsx에서 enableMocking 제거 * feat: 리뷰 그룹 생성 시 코멘트 컴포넌트명 변경 및 기본값 설정 - 컴포넌트명 변경: RevewComment -> ReveweeComments - reviewGroup의 description이 빈문자열이면 기본값을 보여주는 것으로 변경 * refactor: 서버 DB에 있는 리뷰 데이터를 사용하기 위한 상수화 - 현재 DB에 있는 리뷰 데이터를 목 서버에서도 사용하고, 사이드바 페이지 이동 시에도 활용할 수 있도록 관련 value들을 상수화 함 * feat : QueryClient, QueryClientProvider 적용 - src/index.tsx에 QueryClient, QueryClientProvider 적용 * fix: dependencies에 있는 테스트 패키지들을 devDependencies로 옮김 * feat: 리뷰 상세페이지에 react-query 적용 * feat : msw에 리뷰 상세페이지 404 오류 추가 * ci: react-error-boundary 설치 * feat: Outlet에 QueryErrorResetBoundary,ErrorBoundary, Suspense 적용 * feat: 리뷰 상세페이지에 useSuspenseQuery 적용 * refactor: 리뷰 상세페이지 resource, queryString key 상수화 * refactor: 리뷰 상세페이지 react-query key 상수화 * refactor: 리뷰 상세 페이지 컴포넌트 속에서만 사용하는 상수들 상수화 * refactor: DetailedReviewPage/components에 index.tsx를 추가해 import 경로 간결하게 수정 * feat: error 전파를 위한 QueryClient 옵션 추가 - react-query의 query, mutation에서 error가 전파되도록 QueryClient 옵션 설정 * fix: ErrorPage의 SideModal에 closeModal props로 줌 * refactor: ErrorSection 위치 변경(src/pages/ErrorPage -> src/components/error) * feat: ErrorFallback 컴포넌트 생성 * feat: ErrorSuspenseContainer 생성 및 App.tsx에 적용 * chore: constants/index.ts export 경로 변경 - 중복되는 apiErrorMessage 삭제 - queryKeys 추가 * chore: 3차-1주차 핵심 기능 시현 때 필요 없는 코드 주석 처리 * docs: ErrorPage의 ERROR_MESSAGE 수정 * design: formWidth 변경 및 fontSize에 1.4rem 추가 * feat: 리뷰 상세 페이지에 리뷰이 이름 추가 - 리뷰 상세 페이지 목데이터, 데이터 타입에 리뷰이 이름 추가 - 리뷰 상세 페이지 컴포넌트에 리뷰이 이름 추가 및 관련 스타일 추가 * refactor: 불필요한 export 삭제 * chore: type명 수정 (RevieweeCommentProps =>RevieweeCommentsProps) * refactor: ErrorSection으l Button 수정 * refactor: 리뷰 상세 페이지 데이터 타입 변경에 따른 수정 * refactor: ErrorSuspenseContainer 적용 위치 변경 - App가 아닌 router의 element에서 적용하는 것으로 변경 * refactor: 리뷰 상세 페이지 데이터 타입 강제 방법 변경 * chore: 불필요한 주석 삭제 * refactor: ErrorSection의 buttons 네이밍 변경 및 요소에 key 추가 - buttons -> buttonList * chore: 스타일 주석에 NOTE 추가
* feat: 리뷰 미리보기 Co-authored-by: nayonsoso <panda0329@naver.com> * feat: 내가 받은 리뷰 목록 응답 생성 Co-authored-by: donghoony <aru0504@naver.com> * feat: 리뷰 목록 조회 Co-authored-by: nayonsoso <panda0329@naver.com> * refactor: Cascade 적용으로 불필요한 save 제거 Co-authored-by: nayonsoso <panda0329@naver.com> * refactor: 리뷰 미리보기 생성 객체 도출 Co-authored-by: nayonsoso <panda0329@naver.com> --------- Co-authored-by: nayonsoso <panda0329@naver.com>
…com/woowacourse-teams/2024-review-me into fe/feat/140-review-list-react-query
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.
쑤쑤 고생 많았어요.
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.
저엉말 고생많았어요😵💫👍
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.
우하하
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.
😝
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 연동 후, 리액트 쿼리를 적용하고 무한 스크롤을 구현했어요.
🔥 어떻게 해결했나요 ?
변경된 API 적용
size, lastReviewId
가 없어지고revieweeName, projectName
이 추가되었습니다.getReviewListApi를 사용할 때 groupAccessCode를 인자로 받고 헤더에 groupAccessCode를 포함시킵니다.
리액트 쿼리
ReviewPreviewListPage
에서 리액트 쿼리의useInfiniteQuery
훅을 사용해서 리뷰 목록 데이터를 비동기적으로 가져오도록 했습니다.getReviewListApi
를 사용해서 데이터를 불러왔습니다.src/index.tsx
⇒ 리액트 쿼리 적용을 위해QueryClientProvider
추가했습니다.무한 스크롤
useInfiniteQuery
훅을 사용해서 무한 스크롤 기능을 구현했습니다.IntersectionObserver
를 설정했습니다.📝 어떤 부분에 집중해서 리뷰해야 할까요?
api가 계속 변경될 것 같아서 일단은 msw를 사용해서 목 데이터 기반으로 무한 스크롤 기능을 테스트해 주시기 바랄게요!
📚 참고 자료, 할 말