본문 바로가기

프로젝트/나무(나누고 나눔받는 무한 지식 품앗이)

[React & React-Query] 가만히 있어도 데이터 실시간 업데이트 되도록 하기

반응형

오늘은 가장 중요한 부분을 구현했다. 사용자에게 전송된 요청 리스트를 실시간으로 업데이트되도록 하는 부분이다. 이 기능을 위해 react-query를 도입했다고 말해도 과언이 아니다. 물론 isLoading도 무지 잘 썼지만!

내가 만들고 싶었던 건, 촉매 없이 가만히 있어도 요청 리스트가 자동으로 업데이트 되는 것이다. 마치 주식 거래 앱처럼 내가 아무것도 하지 않아도 같은 화면을 보고 있어도 알아서 데이터가 촤라라락 변하는 그런 실시간 업데이트를 도전하였다. 

어제와 마찬가지로 오늘도 다양한 방법을 시도하고 가설을 세워가며 진행하였다. 

간단히 남겨보자.

모달 height 반응형은 아직 추가 못했으니 모른 척 넘어가주기

처음 시도한 것은 리액트 쿼리 문만 수정하는 것이였다.

리액트 쿼리에는 refetch를 위한 다양한 기능이 있어 내가 원하는 조건에 따라 새로운 데이터를 다시 fetch 해오도록 설정할 수 있다.

refetch와 관련된 설정은 다음 링크에 자세히 설명되어 있다.

https://github.com/FE-Lex-Kim/-TIL/blob/master/React/React%20Query%20%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC(3)%20-%20 Background%20 Refetch%20% EC%98% B5% EC%85%98.md#% EC% 9E%90% EB% 8F%99-refetch 

자동 refetch에는 여러 종류가 있는데(위 링크 참고), 나는  브라우저에 focus 되어 있지 않을 때에도 데이터가 refetch 되기 원했기에 쿼리문에 'refetchInterval'과 더불어 'refetchIntervalInBackground'을 추가하였다.

// 여기서 requests는 전역으로 관리되는 로그인 유저의 데이터 중 유저가 받은 요청에 대한 데이터다.
  
  const { data: requestData, isLoading } = useQuery(
    'requestData',
    async () => {
      const requestPromises = requests.map((id) => getRequestById(id));
      const requestList = await Promise.all(requestPromises);

      return requestList;
    },
    {
      refetchInterval: 2000,
      refetchIntervalInBackground: true
    }
  );

하지만 새로운 리스트 데이터를 불러올 수 없었다... 무슨 짓을 해도...

 

두 번째로 시도한 것은 파이어스토어 GET 요청 문을 onSnapShot을 활용하는 방식으로 변경하는 것이었다.

onSnapShot은 간단하게 말하면 데이터를 구독한 후 변경 사항이 있으면 실시간으로 새로운 데이터를 fetch 하는 아이다. 지피티에게 물어보니 아무리 리액트 쿼리문에서 refetch를 위한 설정을 해도 파이어스토어 GET 요청에서 onSnapShot를 사용하지 않으면 원하는 대로 새로운 데이터를 가져오지 않는다고 했다. 사실 이 부분은 왜 그런지 아직 이해를 못 했다. onSnapShot이어야만 리액트 쿼리에서 해준 설정이 의미가 있나? 이 부분은 지피티에게 시간 나면 자세히 물어봐야겠다. 

+ 테스트 해보니 파이어스토어 GET 요청이 onSnapShot으로 되어있지 않아도 정상 작동 된다.

export const getRequestById = (id) => {
  const docRef = doc(db, 'requests', id);

  return new Promise((resolve, reject) => {
    const unsubscribe = onSnapshot(docRef, (docSnap) => {
      if (docSnap.exists()) {
        const requestData = docSnap.data();
        resolve(requestData);
      } else {
        reject(new Error('No such document!'));
      }
    });

    return () => unsubscribe();
  });
};

하지만 여. 전. 히 새로운 리스트 데이터를 불러올 수 없었다... 이때쯤 되니 포기해야 하나 싶었다. 가만히만 있어도 데이터가 업데이트되는 것은 죽었다가 깨어나도 안 되는 것인가~~~ 

 

아무리 생각해도 안되는 이유를 모르겠다며 답답해하는 찰나, 하나의 생각이 머릿속을 스쳤다. 

  const currentUserData = useRecoilValue(userData);
  const requests = currentUserData.userRequests;

  const { data: requestsData, isLoading } = useQuery(
    'requestData',
    async () => {
      const requestPromises = requests.map((id) => getRequestById(id));
      const requestList = await Promise.all(requestPromises);

      return requestList;
    }
  );

requests(유저가 받는 요청 리스트 데이터)는 한 번 로그인했을 때 리코일을 통해 fixed 되는 것이지, 리스트에 데이터가 추가되었다고 리렌더링이 일어나지 않는다. 즉, 무슨 짓을 해도 새로 고침을 하지 않는 이상 requests는 예전 데이터 그대로 남는다. 그러니, 그 데이터를 가지고 map을 돌리고~ 파이어스토어에서 데이터를 가져오고~ 리액트 쿼리문을 수정하고 어쩌고 저쩌고 해도 결국 옛날 데이터를 가지고 지지고 볶는 꼴이다. 

 

궁극적인 문제를 깨달은 후, 바로 코드를 고쳐주었고 5분 만에 그토록 원하던 광경을 볼 수 있었다.

콜백 함수에 유저 데이터를 새로 가져오는 과정을 하나 추가해 주었다! 이렇게 되면 업데이트된 유저 데이터에서 원하는 배열 값을 빼와 실시간으로 데이터를 업데이트해 줄 수 있다. 마치 주식 앱처럼!

  const currentUserData = useRecoilValue(userData);
  const userId = currentUserData.uuid;

  const { data: requestData, isLoading } = useQuery(
    'requestData',
    async () => {
      const { receivedRequests } = await getUserData(userId);
      const requestPromises = receivedRequests.map((id) => getRequestById(id));
      const requestList = await Promise.all(requestPromises);

      return requestList;
    },
    {
      refetchInterval: 2000,
      refetchIntervalInBackground: true
    }
  );

 

궁극적인 원인이 파악되지 않았다면 지금까지도 이거 붙들고 늘어졌을지도 모르겠다. 아찔하다. 

반응형