반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

무한 스크롤 값 저장 방식 수정 with 공식 사이트 본문

wayc 이커머스 프로젝트

무한 스크롤 값 저장 방식 수정 with 공식 사이트

5_hyun 2024. 1. 8. 22:21

이전 구현 방식

https://shortcoding.tistory.com/534

 

전체 상품 불러오기 무한 스크롤 구현 with react-intersection-observer

구현 화면 및 사용 기술 fakejs와 MSW를 이용하여 mock API를 만들어서 구현했다. "react-intersection-observer"를 이용하여 총 아이템 중 마지막에서 4번째 위치에 있는 아이템을 사용자가 보면 데이터를 더

shortcoding.tistory.com

저번에는 무한 스크롤에서 데이터를 불러오고 데이터 저장을

items를 따로 만들어서 하였다.

 

이번에는 공식 사이트에서 준 예시를 보고 구현해 보겠다. 전체 상품을 불러오는 페이지를 구현하는 것은 쉬웠다. 하지만 검색 상품을 가져오는 페이지에서 설정 방법이 좀 달랐다.

구현 방법

-참고 사이트

https://redux-toolkit.js.org/rtk-query/api/createApi#merge

 

createApi | Redux Toolkit

RTK Query > API: createApi reference

redux-toolkit.js.org

우선 이 공식 문서 사이트를 보고 구현했다.

 

-핵심 내용

      // Only have one cache entry because the arg always maps to one string
      serializeQueryArgs: ({ endpointName }) => {
        return endpointName
      },
      // Always merge incoming data to the cache entry
      merge: (currentCache, newItems) => {
        currentCache.push(...newItems)
      },
      // Refetch when the page arg changes
      forceRefetch({ currentArg, previousArg }) {
        return currentArg !== previousArg
      },
  • serializeQueryArgs: 이 설정은 endpoint의 이름을 기억했다가 그 데이터를 캐싱의 키 값 역할을 한다. 검색 페이지에서 이 부분을 건드려야 했다.
  • merge: 이 설정은 현재 캐싱된 데이터와 새로 불러온 데이터를 합쳐주는 부분이다. 이 부분에서 데이터를 수정하려할때 immer에러가 발생한 것을 보면 immer를 사용하여 데이터 수정을 해주는 것 같다.
  • forceRefetch: 이 부분은 페이징 인수가 변경되면 이를 감지하고 새로 refetch를 해주는 역할이다.

모든 상품 불러오는 페이지

  • itemApi.ts에서 이렇게 설정하여 모든 아이템들을 페이징하여 불러왔다. 
  • 새로운 값을 불러오면 merge에서 currentCache에 값들을 합쳤다.

-MainItem.tsx

const MainItem = () => {
  const [page, setPage] = useState(0);
  const { data, error, isLoading } = itemsApi.useGetAllItemsQuery(page);
  const [finalPage, setFinalPage] = useState(false);
  const { ref, inView } = useInView();

  useEffect(() => {
    if (inView && !finalPage) {
      setPage((prev) => prev + 1);
    }
  }, [inView, finalPage]);

  useEffect(() => {
    if (data) {
      setFinalPage(data.finalPage);
    }
  }, [data]);

  return (
    <Wrapper>
      <TitleContainer>
        <h2>전체 상품</h2>
      </TitleContainer>

      {error && <div>새로고침하여 주세요.</div>}

      {isLoading && <div>로딩중...</div>}

      {data && (
        <ItemContainer>
          {data?.items?.map((item: item, index) => {
            return (
              <Link
                to={`/eachitem/${item.itemId}`}
                key={item.itemId}
                ref={data.items.length - 5 === index ? ref : null}
              >
                <ItemBox>
                  <ItemImg>
                    <img src={item.imageUrl} alt={"상품 사진"} />
                  </ItemImg>
                  <ItemInfo>
                    <ItemName>{item.itemName}</ItemName>
                    <span>{item.category}</span>
                    <ItemPrice>{item.basicPrice}원</ItemPrice>
                  </ItemInfo>
                </ItemBox>
              </Link>
            );
          })}
        </ItemContainer>
      )}
    </Wrapper>
  );
};

이 코드를 통해 최종적으로 구현하였다.

상품 검색 페이지

  • 상품 검색 페이지에서는 serializeQueryArgs에서 queryArgs 인자를 추가하여 활용했다.
    • url params인 word를 활용하여 검색하는 상품의 이름이 변경되면 데이터 캐시를 다시 하도록 했다.
  • merge에서는 검색한 상품의 결과가 없을 수도 있기 때문에 타입 가드를 사용해서 구현했다.

-SearchItem.tsx

const SearchItem = () => {
  const path = useLocation();

  const [page, setPage] = useState(0);
  const [finalPage, setFinalPage] = useState(false);
  const { ref, inView } = useInView();
  const { data, error, isLoading } = itemsApi.useGetSearchItemsQuery({
    word: decodeURI(path.pathname.slice(12)),
    page,
  });

  useEffect(() => {
    if (inView && !finalPage) {
      setPage((prev) => prev + 1);
    }
  }, [inView, finalPage]);

  useEffect(() => {
    if (data && "finalPage" in data) {
      setFinalPage(data.finalPage);
    }
  }, [data]);

  useEffect(() => {
    setPage(1);
  }, [path]);

  return (
    <Wrapper>
      <TitleContainer>
        <h2>검색결과</h2>
      </TitleContainer>

      {error && <div>새로고침하여 주세요.</div>}

      {isLoading && <div>로딩중...</div>}

      {data && "message" in data && <NullData />}

      {data && "finalPage" in data && (
        <ItemContainer>
          {(data as ItemPaging)?.items?.map((item: item, index) => {
            return (
              <Link
                to={`/eachitem/${item.itemId}`}
                key={item.itemId}
                ref={
                  (data as ItemPaging)?.items?.length - 5 === index ? ref : null
                }
              >
                <ItemBox>
                  <ItemImg>
                    <img src={item.imageUrl} alt={"상품 사진"} />
                  </ItemImg>
                  <ItemInfo>
                    <ItemName>{item.itemName}</ItemName>
                    <span>{item.category}</span>
                    <ItemPrice>{item.basicPrice}원</ItemPrice>
                  </ItemInfo>
                </ItemBox>
              </Link>
            );
          })}
        </ItemContainer>
      )}
    </Wrapper>
  );
};

상품 검색 페이지는 최종적으로 이렇게 구현했다.

 

-상품 검색 페이지 구현 영상

 

 

728x90
반응형
Comments