결국 이것은 “load more” 동작을 트리거해야 할 시점을 감지하는 데 사용하는 "sentinel"과 사용자 디렉터리 행 렌더링 사이에 발생한 까다로운 "경쟁 조건(race condition)"이었습니다 ![]()
문제
최소 50명의 사용자가 있을 때, /u(사용자 디렉터리) 페이지가 사용자가 아래로 스크롤하기도 전에 첫 로드 시점에 즉시 loadMore를 트리거했습니다. 이로 인해 원치 않는 두 번째 페이지의 결과가 자동으로 로드되었습니다.
근본 원인
초기 페이지 로드 시 타이밍 경쟁 조건:
- 사용자가
/u로 이동합니다. controllers/users가 데이터 로드를 시작합니다 (isLoading: true).- 스피너가 표시되는
ConditionalLoadingSpinner와 함께 템플릿이 렌더링됩니다. - 데이터가 도착하고
isLoading이false가 됩니다. - 스피너가 숨겨지고
DirectoryTable이 50명의 사용자를 렌더링하기 시작합니다. - LoadMore sentinel 요소가 DOM에 삽입됩니다.
- IntersectionObserver가 생성되고 즉시 관찰을 시작합니다.
- 이 시점에서 테이블은 여전히 레이아웃을 계산하고 있으며 전체 높이로 확장되는 중입니다.
- 테이블이 확장되기 전, sentinel이 뷰포트에서 잠시 동안 가시 상태가 됩니다(상단에서 약 292px 위치).
- Observer가 교차를 감지하여
loadMore를 트리거합니다
- 테이블이 최종 높이(약 3689px)로 확장됩니다.
- Sentinel이 올바른 위치(뷰포트 아래, 약 3959px)로 이동합니다.
Observer가 "너무 성급"했습니다 - 콘텐츠가 레이아웃을 완료하기 전에 관찰을 시작하여, 테이블이 최종 높이에 도달하지 않은 짧은 순간에 sentinel을 감지했습니다.
해결 방법
콘텐츠가 준비될 때까지 Observer 생성을 지연시킵니다:
콘텐츠가 여전히 로드 중일 때 IntersectionObserver가 생성되지 않도록 방지하는 @isLoading 파라미터를 LoadMore에 추가했습니다.
현재 작동 방식
페이지 로드 → isLoading=true → Modifier가 Observer 생성을 건너뜀
↓
데이터 로드 완료 → isLoading=false → Modifier가 재실행되어 Observer 생성
↓
테이블이 완전히 확장됨 → Sentinel이 올바른 위치(뷰포트 아래)에 위치
↓
사용자가 아래로 스크롤 → Sentinel이 뷰포트에 진입 → loadMore 트리거 ✓