프로젝트/WINE

무한스크롤 이슈

인재재 2024. 9. 19. 17:17

무한스크롤시 두번 데이터를 받아오는 현상

무한스크롤을 구현하기 위해 아래 처럼 ref를 이용하여 페이지의 끝에 도달하였을때를 감지하여 뒤에 더 불러올 데이터가 있는지 확인하는 wineCursor상태, 데이터를 추가로 불러올 시점을 제어하는 isLoading과 hasMore을 이용하여 로직을 짯다.

  const [wineList, setWineList] = useState<Wine[]>([]);
  const [wineFilterValue, setWineFilterValue] = useState<WineFilterProps>({
    wineType: WineEnum.Red,
    winePrice: { min: 0, max: 100000 },
    wineRating: 0,
  });
  const [wineName, setWineName] = useState("");
  const [wineCursor, setWineCursor] = useState<number | null>(0);
  const [winePriceRange, setWinePriceRange] = useState({ min: 0, max: 100000 });

  const [isLoading, setIsLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const observer = useRef<IntersectionObserver | null>(null);
  const loadMoreRef = useRef<HTMLDivElement | null>(null); // 스크롤 감지할 요소

  const debouncedWineName = useDebounce(wineName, 300); // wineName에  디바운스 적용
  
  async function fetchWines() {
    if (isLoading || !hasMore) return;

    setIsLoading(true);

    try {
      const { list, nextCursor } = await getWines(
        5,
        wineFilterValue,
        debouncedWineName,
        wineCursor,
      ); // 와인 목록 조회


      setWineList((prevWines) => [...prevWines, ...list]); //get한 데이터들 담기
      
      setWineCursor(nextCursor); // 커서 업데이트
      setHasMore(nextCursor !== null); 
    } catch (error) {
      console.error("데이터 가져오기 중 오류 발생:", error);
    } finally {
      setIsLoading(false);
    }
  }

// . . . . 

useEffect(() => {
    const handleIntersection = (entries: IntersectionObserverEntry[]) => {
      if (entries[0].isIntersecting && hasMore && !isLoading) {
        fetchWines();
      }
    };

    observer.current = new IntersectionObserver(handleIntersection);

    if (loadMoreRef.current) {
      observer.current.observe(loadMoreRef.current); // 요소 관찰 시작
    } else {
      console.error("loadMoreRef.current가 설정되지 않았습니다."); // 요소가 없을 경우 에러 로그 출력
    }

    return () => {
      if (observer.current) observer.current.disconnect(); // 컴포넌트 언마운트 시 관찰자 해제
    };
  }, [hasMore, isLoading, wineCursor]); // hasMore, isLoading이 변경될 때 관찰자 업데이트

 

중복된 데이터 에러

 

하지만 문제 발생..  받아온 데이터들을 map으로 돌리는 WienItemList페이지에서 같은 키를 가진 데이터를 또 사용했다는 에러였고 자세히 보니 첫 로딩때도, 스크롤을 내려서 데이터를 다시 불러받을 때도 중복으로 같은 데이터를 두번 받아오는 에러가 있었다.

setWineList((prevWines) => [...prevWines, ...list]);

 

이것저것 건들여보며 분석해본 결과.. 원인은 이쪽에 있었다. wineList가 항상 이전 값(prevWines)과 새로운 값(list)을 병합하여 업데이트되기 때문에, 첫 번째 데이터를 불러올 때도 이전 목록에 데이터를 추가하려고 합니다. 이로 인해 동일한 데이터가 중복되어 두 번 렌더링되는 현상이 발생한 것.

 

if (wineCursor === 0) {
  setWineList(list);
} else {
  setWineList((prevWines) => [...prevWines, ...list]);
}

 

 

 

해결방법은 wineCursor가 0일 때(즉, 처음 데이터를 불러올 때), 이전 목록을 무시하고 새로운 데이터를 설정하도록 처리하게하여 그 이후에는 기존 목록에 새로운 데이터를 추가하여 처음 데이터를 중복으로 추가하지 않고, 이후 데이터만 계속 이어서 받아올 수 있게 되어 문제를 해결하였다.

2. 와인 추천 목록에서 왼, 오른쪽 버튼이 나타나고 사라지는 것을 조정할 때의 문제

기존 좌우 여백을 두고 페이지를 만드는 과정에서 사용했던 스타일은 피그마에 제시된 비율을 정확히 맞추기 위해 아래처럼 1920px의 공간을 만들어놓고 그 안에 요소를 중앙 배치를 하여 정렬하는 방식을 디자인을 하였다.

 <div className="flex max-w-[1920px] flex-col items-center justify-center">
      <div className="flex max-w-[1140px] flex-col gap-6 py-10 max-xl:w-[744px] max-xl:px-6 max-md:w-[375px]">
  // . . . .
      </div>
    </div>

 

 

하지만 문제점은 반응형을 구현할 때 화면을 줄이면 안의 요소들이 뚝뚝 끊겨보인다는 단점이 있었다.

 

그래서 아래와 같이 좌우 마진을 auto로 주고 부드럽게 요소들이 화면에 딸려서 줄어들도록 수정하였다.

  <div className="mx-auto flex max-w-[1140px] flex-col gap-6 py-10 max-xl:mx-[40px]">
  
 // . . .

  </div>