-
InfiniteScroll 컴포넌트 간단구현Language/React 2025. 2. 24. 11:36
react + java + jpa 환경에서 InfiniteScroll을 간단하게 구현해 보자.
React
- views Component
const ReceiptViews: React.FC<ReceiptViewsProps> = (props) => { const [loading, setLoading] = useState<boolean>(false); const [hasMore, setHasMore] = useState<boolean>(true); const paginationRef = useRef<PaginationType>({ current: 1, pageSize: 9 }); const pageNoRef = useRef<number>(1); ... const fetchItems = async () => { setLoading(true); const l_pageNo = pageNoRef.current++; const l_filters = { ...filtersRef.current }; l_filters.receiptYm = props.receiptYm; const params = { pageNo: l_pageNo, pageSize: paginationRef.current.pageSize, ...l_filters, }; const response: ApiReceiptListData = await request.get('/v2/.../receipts', params, { isJson: false }); if (response.status === 200) { if (l_pageNo === 1) { setReceipts(response.items); } else { // 기존 receipts 배열에 새로운 데이터 추가 setReceipts((prev) => [...prev, ...response.items]); } setHasMore(response.hasMore); } setLoading(false); }; ... return ( <> <div className="inner-content box-list-wrap"> ... <> {receipts.length === 0 ? ( <div className="no-data receipt-data-table-empty"> <img src={require('images/image_empty_box.svg').default} alt="" /> <div className="title">해당하는 데이터가 없습니다.</div> </div> ) : ( <div className="items-wrap"> <InfiniteScroll items={receipts} loading={loading} hasMore={hasMore} onLoadMore={fetchItems} loadingComponent={<div className="loading-spinner">Loading more receipts...</div>} > <div className="items-grid"> {receipts.map((item) => ( <ReceiptViewCard receipt={item} onChangeStatus={(l_receipt: ReceiptListItemData) => { handleChangeStatus(l_receipt); }} onReceiptViewPop={(l_receipt: ReceiptListItemData) => { setIsOpenReceiptViewPop(true); setReceipt(l_receipt); }} /> ))} </div> </InfiniteScroll> </div> )} ... </> </div> </> ); }; export default ReceiptViews;
- InfiniteScroll Component
IntersectionObserver API를 사용하여 스크롤 감지하고 스크롤이 특정 지점에 도달하면 onLoadMore함수 실행을 통하여 추가 데이터를 로드하고 로딩 중일 때 로딩 컴포넌트를 표시한다.
import React, { useEffect, useRef, useCallback } from 'react'; interface InfiniteScrollProps<T> { items: T[]; loading: boolean; hasMore: boolean; onLoadMore: () => Promise<void>; children: React.ReactNode; threshold?: number; loadingComponent?: React.ReactNode; } function InfiniteScroll<T>({ items, loading, hasMore, onLoadMore, children, threshold = 0.8, loadingComponent = <div>Loading...</div>, }: InfiniteScrollProps<T>) { const observerRef = useRef<IntersectionObserver | null>(null); const loadingRef = useRef<HTMLDivElement>(null); const handleObserver = useCallback( (entries: IntersectionObserverEntry[]) => { const target = entries[0]; if (target.isIntersecting && hasMore && !loading) { onLoadMore(); } }, [hasMore, loading, onLoadMore] ); useEffect(() => { const currentLoadingRef = loadingRef.current; if (currentLoadingRef) { observerRef.current = new IntersectionObserver(handleObserver, { root: null, rootMargin: '20px', threshold: threshold, }); observerRef.current.observe(currentLoadingRef); } return () => { if (observerRef.current && currentLoadingRef) { observerRef.current.unobserve(currentLoadingRef); } }; }, [handleObserver, threshold]); return ( <div className="infinite-scroll-container"> {children} {(hasMore || loading) && ( <div ref={loadingRef} className="loading-trigger"> {loading && loadingComponent} </div> )} </div> // <>{children}</> ); } export default InfiniteScroll;
java + jpa
- ApiController
haMore로 이후 페이지를 확인한다.
... public class ReceiptController extends BaseController { private final ReceiptService receiptService; @Operation(summary = "수납 내역", description = "") @GetMapping(value = "/receipts") public ResponseEntity<ResponseDto> getList(@ModelAttribute @Valid ReceiptRequestDto.ReceiptListRequest listRequestDto) { ... Page<BaseDto> contentPage = receiptService.getListPage(listRequestDto, memberDTO.getID()); ResponseDto res = ResponseListDto.builder() .status(HttpStatus.OK.value()) .message("성공적으로 조회했습니다.") .items(contentPage.getContent()) .totalElements(contentPage.getTotalElements()) .hasMore(Utils.hasMorePageable(contentPage)) .build(); return new ResponseEntity<>(res, HttpStatus.OK); } }
- Utils
public class Utils { ... public static boolean hasMorePageable(Page<BaseDto> contentPage) { long totalElements = contentPage.getTotalElements(); int pageSize = contentPage.getPageable().getPageSize(); int pageNumber = contentPage.getPageable().getPageNumber(); return ((long) pageSize * (pageNumber + 1)) < totalElements; } }
'Language > React' 카테고리의 다른 글
React로 Gauge Chart 만들기 (1) 2025.02.27 env-cmd란? (1) 2024.12.06 react-hook-form을 사용하여 배열 형태 input 사용 (0) 2024.12.02 타입스크립트 예외처리 (0) 2024.11.22 Swiper - Cannot read properties of undefined (reading 'autoplay') (1) 2024.11.07