-
Notifications
You must be signed in to change notification settings - Fork 103
feat(view): 클러스터 목록 무한 스크롤 구현 #1019
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
Conversation
Add a custom hook that wraps IntersectionObserver API for implementing infinite scroll functionality. The hook provides a ref to attach to a sentinel element and triggers a callback when the element becomes visible. Features: - Configurable threshold and rootMargin - Automatic cleanup on unmount - Can be enabled/disabled via enabled prop 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Implement infinite scroll using IntersectionObserver with sentinel element approach: - Add sentinel element at the end of the list to detect when user scrolls to bottom - Set up IntersectionObserver to trigger onLoadMore callback when sentinel is visible - Update props to accept onLoadMore, isLoadingMore, isLastPage, and enabled - Modify overflow style to hidden (parent will handle scrolling) - Improve author image loading with better error handling and fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Wire up infinite scroll functionality in VerticalClusterList: - Add handleLoadMore callback that fetches next page of commits - Track loading state with isLoadingMore to prevent duplicate requests - Pass props to Summary component for infinite scroll control - Add styles for infinite scroll trigger element 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Remove the manual Load More button UI as it's been replaced with infinite scroll: - Remove handleLoadMore function and related state - Remove load-more-container and load-more-button styles - Simplify loading state check to only show BounceLoader on initial load - Remove unused imports and state subscriptions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Enhance author avatar handling to gracefully handle missing or failed images: - Update AuthorInfo type to allow undefined src - Add fallback avatar showing first letter of name when src is undefined - Improve isGitHubUser check to handle undefined values - Update AuthorBarChart to handle undefined profile images 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Add validation check to ensure clusterNodes is a valid array before processing: - Check if clusterNodes is an array before using it - Log error and stop loading if invalid data is received - Prevents crashes from malformed pagination responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Streamlined code comments in the Summary component by removing redundant explanations and translating Korean comments to English for better international collaboration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
- Create IntersectionObserver only once and reuse it for performance. - Add `isObservingRef` to prevent redundant observer operations. - Improve observer lifecycle management for infinite scrolling.
Updated COMMIT_COUNT_PER_PAGE constant to load more commits per page, improving user experience by reducing the number of load operations needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
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.
너무너무 수고하셨습니다!! 👍👍
| if (!isLastPage && stopIndex >= clusters.length && sentinelRef.current && enabled) { | ||
| // Clean up existing observer if present | ||
| if (observerRef.current) { | ||
| observerRef.current.disconnect(); |
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.
여기서 observer를 매번 지우는 이유가 따로 있을까요?? IntersectionObserver의 observe()로 매번 자동 감시가 되는 걸로 알고 있어서요!!
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.
@yuminnnnni
의견 너무 감사합니다ㅎㅎ
isLoadingMore ,enabled 로 다음 페이지 호출 여부를 컨트롤하게 되는데, enabled, isLoadingMore가 변경될 때 IntersectionObserver 콜백함수가 생성시점의 값을 클로저로 가지고 있어서 변경된 상태값이 반영되지 않습니다.
그래서 변경될 때마다 Observer를 다시 등록해주도록 구현하였습니다ㅎㅎ
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.
지금보니 useRef로 enabled, isLoadingMore를 컨트롤 해주었으면 어떨까싶네요ㅎㅎ 추후 작업때 반영해보겠습니다ㅎㅎ
ytaek
left a comment
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.
그냥 lib 붙이면 되는게 아니라 신경쓸게 꽤 많았군요!!
고생하셨습니다!!!! LGreatTM
|
|
||
| useEffect(() => { | ||
| if (initRef.current === false) { | ||
| if (!initRef.current) { |
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 isGitHubUser = (src: string): boolean => { | ||
| return src.startsWith(GITHUB_URL); | ||
| const isGitHubUser = (src: string | undefined): boolean => { | ||
| return Boolean(src?.startsWith(GITHUB_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.
코드 여쭤보려다가 copilot 한테 물어봤습니다.
'''
GitHub Copilot Chat Assistant
간단히: src가 undefined일 수 있어서요. src?.startsWith(...)는 src가 없으면 undefined를 반환하고, 함수는 항상 boolean을 반환해야 하므로 Boolean(...)으로 감싸서 undefined를 false로 바꿔 일관된 boolean을 반환하게 한 것입니다.
세부:
src?.startsWith(GITHUB_URL) 결과는 true | false | undefined 입니다 (src가 없으면 undefined).
Boolean(undefined) → false 이므로 함수 시그니처(boolean)를 충족하고 호출부에서 안전하게 사용 가능합니다.
대체로 쓸 수 있는 깔끔한 패턴들:
return src?.startsWith(GITHUB_URL) ?? false; // optional chaining + nullish coalescing
return !!src && src.startsWith(GITHUB_URL); // !!로 boolean 캐스트
return (src ?? "").startsWith(GITHUB_URL); // 빈 문자열로 대체
'''
copilot 잘하네요 ㅋㅋ
| }; | ||
|
|
||
| const AvatarComponent = ({ name, src }: AuthorInfo) => { | ||
| // src가 undefined인 경우 이름의 첫 글자를 표시하는 기본 아바타 사용 |
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.
주석 삭제했습니다! 확인 감사합니다ㅎㅎ
| if (!isLastPage && stopIndex >= clusters.length && sentinelRef.current && enabled) { | ||
| // Clean up existing observer if present | ||
| if (observerRef.current) { | ||
| observerRef.current.disconnect(); |
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 getRowHeight = ({ index }: { index: number }) => { | ||
| if (!isLastPage && index === clusters.length) { | ||
| return 10; |
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.
반영했습니다! 의견 감사합니다ㅎㅎ
- Extract repeated sentinel row condition into isSentinelRow helper function in Summary component - Remove Korean comment from Author component for code consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
🎯 목적
기존의 "Load More" 버튼을 제거하고 무한 스크롤(Infinite Scroll) 기능을 구현하여 사용자 경험을 개선합니다. 사용자가 스크롤을 내리면 자동으로 다음 페이지의 커밋 데이터를 로드하여 끊김 없는 탐색 환경을 제공합니다.
📋 주요 변경사항
1. 커스텀 훅 추가
useInfiniteScroll훅 구현 (packages/view/src/hooks/useInfiniteScroll.ts)2. Summary 컴포넌트 개선
Infinite Scroll 통합 (
packages/view/src/components/VerticalClusterList/Summary/Summary.tsx)onLoadMore,isLoadingMore,isLastPage,enabledprops 추가Author 이미지 에러 핸들링 개선 (
packages/view/src/components/Common/Author/Author.tsx)AuthorInfo타입에서src속성을 optional로 변경isGitHubUser체크 로직에서 undefined 값 처리3. VerticalClusterList 연동
packages/view/src/components/VerticalClusterList/VerticalClusterList.tsx)handleLoadMore콜백 추가 (다음 페이지 커밋 fetch)isLoadingMore상태 관리로 중복 요청 방지4. App 컴포넌트 리팩토링
packages/view/src/App.tsx)handleLoadMore함수 및 관련 상태 제거5. 데이터 검증 강화
useAnalayzedData훅 개선 (packages/view/src/hooks/useAnalayzedData.ts)clusterNodes가 유효한 배열인지 검증🔗 관련 이슈
Closes #1003
공유할 내용
다음 클러스터 목록을 빌드하는 동안 로딩을 표시할 스피너 표시는 해당 PR에서 구현하지 않았습니다. 다른 PR 에서 구현할 생각인데 최종 보고서 전에는 불가할 것 같습니다.