Skip to content

Instantly share code, notes, and snippets.

@wseungjin
Created February 17, 2021 07:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wseungjin/6d19cdcdc8736c911198769ce853ae27 to your computer and use it in GitHub Desktop.
Save wseungjin/6d19cdcdc8736c911198769ce853ae27 to your computer and use it in GitHub Desktop.
SWR 무한 스크롤 데이터 삽질기

SWR 삽질기

개발하면서 SWR을 사용하면서 삽질한 내역을 정리한다.. 하루정도 삽질했다..ㅠ

SWR 을 선택한 이유

먼저 SWR 라이브러리를 우리가 선택한 이유!!

swr은 Polling on interval, Revalidation on focus, Revalidation on network recovery , Local mutation (Optimistic UI) 등의 기능을 지원해, 클라이언트와 서버 사이의 데이터 정합성을 라이브러리가 어느정도 해결해 줌으로 크게 따로 처리하지 않아도 실시간적인 서비스를 제공 할 수 있어, 선택하였다.

SWR Apollo Client에서 사용하는 것처럼 캐시에 없으면 데이터를 fetch하고, 있으면 캐시에서 가져다 사용하는데 여러 경험한 이슈가 많아서 소개하려고 한다.

이슈 1

SWR에 key로 graphql 쿼리와 같이 보낼 variables를 object의 형태로 넣었더니, data를 받아오지 못하는 현상이 발생

SWR의 내부동작

SWR이 key Arguments를 every render마다 Shallow하게 Compare하기 때문에, variables 객체가 새로운 객체라고 판단되어, 변화가 되었다고 판단해 revailidation을 반복하는 이슈가 발생하였다.

해결 방법

필요한 variables를

[ .... , key1, value1, key2, value2 ]

의 형태로 보내서 해결하였다.

무한 스크롤로 가는 여졍에서 맞은 이슈

글의 데이터를 전부 가져오면 성능상에 문제가 생기고, 유저도 모든 데이터가 필요 없기 때문에 20개씩 끊어서 데이터를 가져오는 무한 스크롤을 이용하려고 진행했는데 SWR에서 제공하는 useSWRInfinite 를 사용할 때 계속 렌더링이 되는 이슈가 발생했다.

계속 렌더링이 되는 것도 이상한데 데이터가 여러개 생겼다가 없어졌다 막 이래서 도대체 무슨 이슈인지 감이 안잡혀 오랫동안 시간을 소요하게 됬다.

SWRInfinite와 무한 스크롤을 사용할 때 Apollo Client와 다른점

Apollo Client는 fetchMore 을 통해 isIntersecting 될때 더 요청하는 방식으로 진행해 쉽게 해결 가능했는데, SWR에는 그런 함수가 없었다.


  useEffect(() => {
    const asyncEffect = async () => {
      if (!intersecting || !fetchMore) return;
      const { data: fetchMoreData }: any = await fetchMore({
        variables: { [moreVariableTarget]: bottomId },
      });
      if (!fetchMoreData) setIntersecting(false);
      if (fetchMoreData[dataTarget].length < 20) setLoadFinished(true);
    };
    asyncEffect();
  }, [intersecting]);

첫번째 발견한 현상

무한 스크롤을 위해서 마지막 데이터의 id값을 이용해서 요청 할 때 데이터가 바뀌자 마자 bottomId를 업데이트를 시켜줬는데, 이 결과 결국 key값이 바로 변경된 걸로 처리되어 계속 렌더링을 하는 이슈를 발견했다.

  const { _id: bottomId } = data?.[dataTarget][data?.[dataTarget].length - 1] || {};
  const getKey = (){
  return [... , "bottomId" , bottomId]
  }
  
  const { data } = useSWRInfinite(getkey, fetcher);

두번째 발견한 현상

위의 기점을 해결하기 위해서 intersection 할때만 bottomId를 변경하는 방식으로 진행했는데, 그렇게 하니 이전의 데이터를 합치지않고 새로 bottomId로 받아온 값만 계속 렌더링 해주는 현상을 발견하였다.

이게 size가 1이기 때문에 setSize를 통해 bottom id를 변경 해줄시 size를 1늘리면 문제가 없을 거라고 생각했는데, size가 intersecting 할때 계속 지속적으로 늘지 않고 널뛰기 하면서 무한 렌더링하는 상황이 발생했다.

원인

getKey

원인은 swrInfinite에 넣는 매개변수 getKey에 있었다.

The getKey function is the major difference between useSWRInfinite and useSWR. It accepts the index of the current page, as well as the data from the previous page.

  • 공식문서 첨부 -

getKey는 현재 페이지 뿐만 아니라 모든 페이지의 key를 리턴해 가져오는 함수인데, 값을 고정해서 사용하니 한 페이지의 key만 계속 사용해 size가 널뛰고, size가 널뛰면서 받아오는 데이터도 사라지는 등의 이슈가 생긴것이다.

SWR Infinite의 내부동작

getKey를 기준으로 현재 페이지 kee, 모든 이전 페이지 key 를 가져와 모든 총 페이지를 요청한다

ex) 0 페이지를 요청 0 페이지 key 받아서 처리

1페이지를 요청 0 페이지 key 받아서 처리 => 캐시 되어 있음 1 페이지 key 받아서 처리

이런식으로 계속 반복되어 처리되기 때문에

하나로 고정하면 안되고 getKey 의 매개변수 pageIndex, prevData를 이용해 변화에 따른 데이터 key를 만들어야 한다.

해결 방안

  const getKey = (pageIndex, prevData) => {
    if (prevData && prevData.productList.length < 20) {
      setLoadFinished(true);
      return null;
    }

    const targetBottomId =
      prevData?.[targetData][prevData?.[targetData].length - 1]._id || undefined;

    return [graphqlQuery, accessToken, targetBottomData, targetBottomId, ...variableArray];
  };

위처럼 prevData를 확인하고 데이터가 존재할시 데이터의 마지막 id를 가져오는 방식으로 다음페이지의 데이터가 그 마지막 데이터의 이전 데이터로 계속 유지 되게 하였다.

결론

SWR의 핵심은 key를 통한 데이터 요청이다 key관리와 내부 동작을 이해하지 못하면, 삽질을 한다...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment