본문 바로가기
스파르타코딩클럽/내일배움캠프

[TIL] 내일배움캠프 React 과정 2023.01.09

by heereal 2023. 1. 10.

Today I Learned

  • React Native 팀 프로젝트 진행

 


Firestore onSnapshot과 useQuery

const getComments = async () => {
  const q = query(
    collection(dbService, "communityComments"),
    orderBy("date", "desc")
  );

  const array = [];
  await onSnapshot(q, (snapshot) => {
    snapshot.docs.map((doc) => (
      array.push({
      id: doc.id,
      ...doc.data()
    })))
    setComments(array)
  })
  return array;
};

const data  = useQuery("communityComments", getComments)

기존에 useState를 이용해서 데이터를 불러왔던 것을 useQuery를 이용해서 데이터를 불러올 수 있게 리팩토링하고 있는데 어떤 때는 data에 빈 배열이 들어가고, 어떤 때는 firesotre에서 불러온 데이터가 정상적으로 들어가기도 한다.

 

내 예상으로는 onSnapshot으로 데이터를 가져오기 전에 array에 할당한 빈 배열이 먼저 반환되는 상황인 거 같다. 그래서 await를 써보려고 했지만 어떤 이유에서인지 onSnapshot에는 await를 사용할 수 없는 거 같다.

 

  • When you use get() you retrieve all the documents of the collection only once (like a "get and forget").
  • When you use onSnapshot() you constantly listen to the collection.

Note that onSnapshot() is not an asynchronous method, while get() is => don't call onSnapshot() with await.

onSnapshot은 데이터에 변화가 생길 때마다 데이터를 실시간으로 업데이트한다는 속성 때문에 비동기 함수를 사용할 수 없는 거 같다. 그래서 await를 사용할 수 없던 거였다.

참고 https://stackoverflow.com/questions/64521755/firestore-onsnapshot-or-async-await-issue-or-both

 

const getComments = async () => {
  const q = query(
    collection(dbService, "communityComments"),
    orderBy("date", "desc")
  );

  const array = [];
  const querySnapshot = await getDocs(q)
  querySnapshot.forEach((doc) => array.push({
    id: doc.id,
    ...doc.data()
  }))
  return array;
};

그래서 async/await를 사용하기 위해서 onSnapshot을 getDocs로 수정했다. getDocs로 await를 사용하니까 어떤 경우에서든 일관되게 데이터를 성공적으로 불러온다.

 

const { data: comments, isLoading }  = useQuery("communityComments", getComments)

if (isLoading || isRefreshing) {
  return (
    <Loader>
      <ActivityIndicator size="large" />
    </Loader>
  )
}

그런데 데이터를 받아오기 전에는 array가 undefined인 상태이기 때문에 로딩 중일 경우에 array를 쓰지 않아도 되도록 따로 처리를 해주어야 한다. 일단은 임시로 로딩 중인 상황에 ActivityIndicator를 내보내도록 if문을 추가했다.

 

useQuery만 사용했을 때는 댓글을 추가, 수정, 삭제했을 때  실시간으로 업데이트가 되지 않고 새로고침을 한 번 해야 변경된 데이터가 반영되기 때문에 이 부분을 추가적으로 수정해야 한다.

 

useQuery 업데이트할 때 참고할 자료

https://blog.pumpkin-raccoon.com/118

https://thinkforthink.tistory.com/340

https://kdinner.tistory.com/113

 

 

다음 TextInput에 focus되도록 하기

  1. <TextInput ref={pwInput} />
  2. <TextInput onSubmitEditing={() => pwInput.current.focus()} />

만약에 로그인 화면이라고 가정했을 때 이메일을 입력하고 입력 버튼을 누르면 바로 비밀번호 인풋에 포커스가 가도록 구현하고 싶었다. 검색해 보니 useRef를 사용해서 구현할 수 있었다. 비밀번호 TextInput에 ref를 설정하고 아이디 TextInput에 onSubmitEditing을 비밀번호 TextInput.current.focus()를 주면 된다.

 

참고 https://stackoverflow.com/questions/32748718/react-native-how-to-select-the-next-textinput-after-pressing-the-next-keyboar

 

 

RefreshControl 추가

const [isRefreshing, setIsRefreshing] = useState(false);

// 새로고침하기
const onRefresh = async () => {
  setIsRefreshing(true);
  await queryClient.refetchQueries("communityComments");
  setIsRefreshing(false);
};

if (isLoading || isRefreshing) {
  return (
    <Loader>
      <ActivityIndicator size="large" />
    </Loader>
  )
}

<Wrap 
  refreshControl={
    <RefreshControl 
      refreshing={isRefreshing} 
      onRefresh={onRefresh}
    />
  }    
>

useQuery로 리팩토링하는 과정에서 firestore 데이터를 불러오기 위해 onSnapshot->getDocs로 바꿨기 때문에 댓글 데이터에 변경 사항이 생겼을 때 새로고침을 해야만 업데이트된 데이터를 볼 수가 있다. 그래서 원래는 팀원들과 작업물 다 합치고 ScrollView->FlatList로 리팩토링 하면서 한번에 하려고 했는데 편하게 작업하기 위해서 미리 RefreshControl을 추가했다. 

 

new Date()로 작성 날짜 및 시간 저장하기

내가 원했던 것은 댓글을 추가할 때 'date'에 작성 날짜와 시간을 저장해서 데이터를 불러올 때 'date'를 이용해서 내림차순 정렬을 하는 것이었다.(댓글을 최신순으로 보여주기 위해서!) 근데 new Date() 그대로 저장하면 저장된 값이 문자열이 아니기 때문에 화면에 작성 날짜를 보여주기 위해서 추가적으로 가공을 해주어야 했다.

 

내가 생각했던 방법은 일단 new Date()로 DB에 저장을 하고 저장한 값을  추후 가공하는 것이었는데 아예 댓글을 작성할 때 가공한 날짜 정보를 올리는 방법이 더 쉬운 거였다.

 

export const postTime = () => {
  const date = new Date();
  const year = date.getFullYear();
  const month = ("0" + (date.getMonth() + 1)).slice(-2);
  const day = ("0" + date.getDate()).slice(-2);
  const hours = ("0" + date.getHours()).slice(-2);
  const minutes = ("0" + date.getMinutes()).slice(-2);
  const seconds =   ("0" + date.getSeconds()).slice(-2);
  const dateString = year + month + day + hours + minutes + seconds;

  return {
    dateString
  };
};

이를 위해 다음과 같은 함수를 만들어서 댓글을 작성할 때 date: dateString으로 저장을 한다.

 

export const getDate = (date) => {
  return `${date.slice(0,4)}/${date.slice(4,6)}/${date.slice(6,8)}`
}

그러면 DB에 "20230109171500"와 같이 문자열 형태로 들어가게 되는데 이것을 화면에 보여주기 위해서 slice 메서드를 이용해서 가공을 한다. 이건 어느 페이지에서든 쓸 일이 많을 거 같아서 아예 함수화 시켜서 쓸 수 있게 했다. 그럼 결과적으로 20230109171500을 2023/01/09로 전환해서 화면에 작성 날짜를 보여줄 수 있다.

 


회고

오늘도 포기하지 않는 쿼리 공부... 오전에 튜터님 도움을 받아서 새로운 해결 방법을 찾아냈다. 그 결과 useQuery로 데이터 불러오는 데는 성공했다! 이제 useMutation을 공부해야 하는데 마침 저녁에 리액트 쿼리 특강이 있어서 쿼리 기초부터 차근차근 이해할 수 있었다. 내일은 useMutation이랑 Invalidation 사용해서 데이터 실시간 업데이트되도록 만드는 게 목표다.

 

 

 

 

댓글