교차대상 뷰포트 영역 재지정 observer

sudyn·2023년 6월 15일
0

TIL

목록 보기
7/10

IntersectionObserver는 지정된 대상 요소와 뷰포트의 교차 상태를 관찰합니다. ObserverContainer는 교차 상태를 감지할 대상 요소로 사용되는 것 같습니다. 하지만 코드에서 ObserverContainer는 빈 div 요소로 선언되어 있고, ref={containerRef}를 통해 containerRef와 연결되어 있습니다. 따라서 ObserverContainer 요소 자체가 교차하지 않아도 IntersectionObserver의 콜백 함수가 호출됩니다.

만약 ObserverContainer가 아닌 placeList와 같은 실제 데이터를 로드하는 요소를 교차 상태 감지의 대상으로 사용하고자 한다면, 해당 요소의 참조를 containerRef에 할당해 주세요. 예를 들어, placeList를 렌더링하는 부분을 로 대체하여 교차 상태 감지를 원하는 요소에 대한 참조를 가져올 수 있습니다.

import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { styled } from 'styled-components';
import SearchPlaceItem from './SearchPlaceItem';
import Loading from '../Loading';
import { ReactComponent as SearchInfo } from '../../assets/map/search-info.svg';
import { ReactComponent as NoSearchInfo } from '../../assets/map/no-search-info.svg';
import useIntersectionObserver from '../../hooks/useIntersectionObserver';

export default function SearchPlaceList({ searchPlace, currentLocation }) {
  const MAX_PAGES = 5;
  const containerRef = useRef(null);
  const [isLastPage, setIsLastPage] = useState(true);
  const [isLoading, setIsLoading] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const currentPageRef = useRef(currentPage);
  const [isFirstLoad, setIsFirstLoad] = useState(false);

  const { kakao } = window;
  const FOOD_CATEGORY_CODE = 'FD6'; // 음식점 카테고리 코드
  const ps = new kakao.maps.services.Places();

  const searchOptions = useRef({
    size: 10,
    page: currentPageRef.current,
    category_group_code: FOOD_CATEGORY_CODE, // 음식 카테고리 코드
    location: null,
    radius: 10000, // 기본 검색 반경 설정 (미터 단위)
  });

  if (currentLocation) {
    searchOptions.current.location = new window.kakao.maps.LatLng(
      currentLocation.latitude,
      currentLocation.longitude,
    );
    searchOptions.current.radius = 2000; // 검색 반경 설정 (미터 단위)
  }

  const fetchSearchResults = async (keyword, options) => {
    if (!keyword) {
      return []; // 검색어가 없는 경우 빈 배열 반환
    }

    try {
      const { data, status } = await new Promise((resolve, reject) => {
        ps.keywordSearch(
          keyword,
          (data, status) => {
            if (status === kakao.maps.services.Status.OK) {
              // if (data.length > 0) {
              //   setIsLastPage(false); // 데이터가 존재하는 경우 마지막 페이지가 아님
              // } else {
              //   setIsLastPage(true); // 데이터가 없는 경우 마지막 페이지임
              // }
              resolve({ data, status });
            } else {
              reject(new Error('검색에 실패했습니다.'));
            }
          },
          options,
        );
      });

      console.log('isLastPage', isLastPage);
      return data;
    } catch (error) {
      console.log('검색에 실패했습니다.', error);
      return [];
    }
  };

  const {
    data: placeList,
    isError,
    refetch,
  } = useQuery(['searchPlace', searchPlace, searchOptions.current], () =>
    searchPlace ? fetchSearchResults(searchPlace, searchOptions.current) : [],
  );

  const loadNextPage = useCallback(() => {
    console.log('페이지체크', isLoading, currentPage);
    if (!isFirstLoad && currentPage < MAX_PAGES && !isLoading) {
      setCurrentPage(prevPage => prevPage + 1);
    }
  }, [isLoading, currentPage, isFirstLoad]);

  useEffect(() => {
    console.log('Current Page:', currentPage);
    console.log('Total Place List:', placeList);
  }, [currentPage, placeList]);

  // 마지막 페이지일때 로딩되지 않도록
  useEffect(() => {
    if (currentPage >= MAX_PAGES) {
      setIsLastPage(true);
    }
  }, [currentPage]);

  // observer 감지
  const options = { threshold: 1 };
  const [observe, unobserve] = useIntersectionObserver(loadNextPage, options);

  useEffect(() => {
    console.log('ref', containerRef.current);
    if (containerRef.current) {
      observe(containerRef.current);
    }

    return () => {
      if (containerRef.current) {
        unobserve(containerRef.current);
      }
    };
  }, [observe, unobserve]); // observe와 unobserve를 의존성 배열에 추가

  useEffect(() => {
    setIsFirstLoad(true);
  }, []);

  // useEffect(() => {
  //   setIsLastPage(false); // 검색어가 변경되면 마지막 페이지 초기화
  //   setCurrentPage(1); // 검색어나 위치 변경 시, currentPage 초기화
  //   setIsLoading(false); // 검색어나 위치 변경 시, 로딩 상태 비활성화
  //   refetch(); // 검색어나 위치 변경 시, fetchSearchResults 재요청
  // }, [searchPlace, currentLocation]);

  return (
    <div>
      <ListWrapper>
        {placeList && placeList.length > 0 ? (
          placeList.map((place, index) => <SearchPlaceItem key={index} place={place} />)
        ) : (
          <DefaultContainer>
            {!searchPlace && (
              <>
                <SearchInfoIcon />
                <InfoMsg>장소를 검색해주세요!</InfoMsg>
              </>
            )}
            {searchPlace && (
              <>
                <NoSearchInfo />
                <InfoMsg>검색 결과가 없습니다.</InfoMsg>
                <InfoMsg>다른 단어로 검색해보세요.</InfoMsg>
              </>
            )}
          </DefaultContainer>
        )}
        {isLoading && <Loading />}
      </ListWrapper>
      <ObserverContainer ref={containerRef} />
    </div>
  );
}

const ListWrapper = styled.div`
  width: 360px;
  height: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  overflow: hidden;
`;

const DefaultContainer = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: calc(100vh - 270px);
`;

const SearchInfoIcon = styled(SearchInfo)`
  width: 48px;
  height: 48px;
`;

const InfoMsg = styled.h4`
  color: var(--color-gray-700);
`;

const ObserverContainer = styled.div`
  height: 36px;
  bottom: 0;
  background: yellow;
`;
profile
개발계발하는 프론트엔드 개발자🍠

0개의 댓글