방구석에 놔둔 개발 노트

1년차 웹 / 앱 프론트엔드 엔지니어의 좌충우돌 얼렁뚱땅 앞뒤짱구 생존기

2023-02-26: 개인 프로젝트 도전기 (6주차)

목표 정산 - 결국 집중력이 와장창 무너진 한 주

이번 주에 목표로 둔 사항들은 아래와 같다.

  • 관리자 페이지 View 구현 진행
    • 현재 대기 상황, 상점 관리, 설정, 비밀번호 변경, 회원 탈퇴
  • 폰트, 공용 색상 등 선정
    • 프리텐다드로 폰트 적용했는데, 제대로 적용되지 않는 듯하여 다시 확인해봐야겠다.
    • 색상은 Adobe Color에서 찾아보기로.
      • 이왕이면 색상에 의미를 부여하고 싶다.
  • 각 페이지별 라우팅 연결 등 설정
    • 일부 페이지에서는 다른 페이지로 넘어가져야하는 내용들이 있어서 그 내용들을 배치해보고자 한다.
  • 인증 문제 등 발생 시 출력할 Toast 메시지 각 페이지 별로 배치
    • 현재는 임시로 console.log()를 적어놨는데 이번 기회에 다 수정하기로.
    • 앞서 View를 구현한 관리자 로그인, 회원가입 등의 페이지들의 Firebase 기능 연동
    • Firebase Auth의 기능들을 적극적으로 활용

보다시피 할 것은 많았는데도 불구하고, 작업된 것이 별로 없는데 그 이유는 Firebase 연동을 작업했으니 우선적으로 대기 입력 페이지와 대기 상태 페이지에 보여질 정보들을 Firestore의 컬렉션과 문서들을 직접적으로 연결시켜 구현하기 위해서였다.

관리자 기능이 전부 구현되면 옵션 기능을 받아올 정보라던가, 추가한 이미지를 배경으로 설정할 수 있도록 한다던가 등 관리자 기능이 모두 구현되어야 할 수 있는 작업들이 남아있지만 현재 만들어진 선에서는 가게에서 손님 대기와 관련한 기능들은 다 동작하게 구현해 놨다.

단점이라면, 이 작업을 하면서 2~3일 동안 쿼리 데이터가 갱신되지 않는 문제로 삽질을 좀 크게 했어서 멘탈이 나갔다는 점이다.

해결한 내용은 굉장히 간단했기 때문에 더 허탈했고, 그 덕분에 좀 현실로부터 회피하기 위해 잠시 게임에 집중하기도 했다(….)

그로 인해 진행이 엄청 더뎌졌지만, 대신 그 삽질을 통해 조금이라도 정리해볼 만한 내용들이 있으니 플러스 마이너스 제로라고 여기기로 했다.

대기 상태 페이지의 객체 정보 받아오기

위에서도 말했지만 이번에 Firebase 연동도 마쳐놨고 페이지 간 이동을 구현하면서 본격적으로 손님의 데이터가 페이지에 보여질 수 있도록 목표와는 별개로 해당 작업에 좀 더 집중을 했다.

작업 중에 대기 상태 페이지와 관련하여 이 페이지의 path를 ‘/(가게 이름)/waitingstate/(손님의 전화번호)’로 설정해주고 있이 URL에 있는 손님의 전화번호 정보를 이용해 웨이팅 리스트에서 찾아내고, 그 정보를 페이지에 반영하는 식으로 내용을 구성해보고자 했다.

//waitingList는 Firebase 내에 있는 데이터를 받아오고 있다.
const currentUserIdx = waitingList.data?.findIndex(
    (elem: UserData) => elem.tel === telnumber
  );

const currentUserData = waitingList.data?.[currentUserIdx];

옵셔널 체이닝을 이용해서 현재 useQuery를 통해 받아온 대기열 정보가 있다면, 이를 findIndex 메소드를 통해서 배열 내에서 입력한 전화번호 정보와 동일한 값을 가진 객체를 유저의 index로 잡고, 배열 내에서 그 index에 해당하는 정보는 다른 변수를 통해서 받아오도록 했다.

위의 내용을 구현하는 과정 중에서, useQuery를 통해 데이터를 받아올 때 한 가지 문제점이 있었는데 firestore에서 컬렉션의 문서들을 가져오면 그 순서가 추가된 유저들의 순서대로 받아오는 것이 아니라는 점이다.

그로 인해서 필터를 해줄 필요가 있었고, 이 방법을 어떻게 할까 고민하다가 백엔드 친구들이 데이터를 작업할 때 Timestamp를 추가했던 점이 떠올라 이 값을 이용해 데이터를 한 번 더 정렬해 값을 받아오도록 유도했다.

우선 이 작업을 하기에는 고객의 대기 정보를 추가 시에 Timestamp를 만들어 줄 사항이 필요했다.

const sendWaitingDataToDatabase = async (userInfo: UserData) => {
    const sendUserData = {
      ...userInfo,
      createdAt: new Date().getTime(),
            // 유저가 기본적으로 입력한 정보 외에, createdAt이라는 key를 추가해 Timestamp를 넣었다.
    };
    const addWaitingData = await setDoc(
      doc(db, `${store}`, userInfo.tel),
      sendUserData
    )
      .then((data) => data)
      .catch((error) => error.message);
    return addWaitingData;
  };

위와 같이 추가한 Timestamp는 이제 배열 내에서의 순서를 정렬하기 위한 기능으로서 사용하게 된다.

기존에 유저의 대기 상태 페이지에서 대기 정보를 받아오는 useQueryFn 부분에 대기열을 받아온 후, 이를 시간 순으로 정렬하기 위해 한 번 더 .sort() 메소드를 이용해 가공하도록 했다.

const getWaitingData = async () => {
    const waitingState = await getDocs(waitingCol).then((data) => {
      const list: any = [];
      data.forEach((doc) => {
        list.push(doc.data());
      });
            // 아래와 같이 오름차순 정렬을 통해서 데이터들이 시간순으로 정렬되도록 했다.
      list.sort(function (a: any, b: any) {
        return a.createdAt - b.createdAt;
      });
      return list;
    });
    return waitingState;
  };

우선 여기까지 한 것은 좋았는데, 이제 남은 것은 그래서 path에 있는 가게 이름과 손님의 전화번호를 어떻게 받아오냐는 점이었다.

이 부분은 사용중인 react-router-dom 라이브러리에서 지원하는 useParams 훅을 사용하기로 했다.

const WaitingStateContainer = () => {
  const visionState = useRecoilValue<boolean>(lowVisionState);
  const [cancelState, setCancelState] = useState(false);
    // URL 상의 파라미터 값들을 가져와주는 react-router-dom의 Hook이다.
  const { store, telnumber } = useParams();

    const db = getFirestore();
    // 위에서 받아온 store 값을 이용해 collection을 받아오기로 했다.
  const waitingCol = collection(db, `${store}`);

    // 아래의 유저의 순서를 가져오기 위한 부분으로서 telnumber 값을 활용했다.
    const currentUserIdx = waitingList.data?.findIndex(
    (elem: UserData) => elem.tel === telnumber
  );

이런 방식을 통해서 유저의 정보를 받아올 수 있도록 구현했고, 잘 작동되는지 테스트해본 결과 정상적으로 데이터를 출력하는 것을 확인했다.

여기까지는 좋았다. 여기까지는 말이다…. 문제는 그 다음이었다.

쿼리 키 하나 잘못 적어 발생한 2-3일의 삽질기 - InvaildQueries

위와 같은 과정을 구현하고 테스하던 도중에 한 가지 문제점이 발생했다.

우선 손님의 대기 정보를 입력한 후 전송한 다음에, 대기 상태 페이지를 이동하는 건 잘 동작하는데, 해당 페이지에서 바로 뒤로가기를 누른 다음에 다시 새로 대기 정보 입력 후에 전송해서 대기 상태 페이지로 넘어가는 걸 테스트해보니 페이지가 노출되지 않는 문제가 발생했다.

왜 그런지 console.log를 통해 체크해보니까 대기 상태 페이지에서 대기열이 최신으로 갱신되지 않고 이전 상태 그대로가 유지되다보니 새로 들어온 데이터가 포함되지 않아 이런 문제가 발생하고 있는 것을 확인했다.

이 문제를 해결하기 위해 트식인과 Firebase의 공식 문서, React-Query의 공식 문서 등을 통해 찾아본 결과 세 가지 방법으로 해당 문제를 해결해보려고 시도해봤다.

우선, 가장 해결책에 가까운 것은 아래의 방법이었다.

useMutataion이 성공적으로 작동해 상태가 onSuccess로 가면 React-Query의 queryClinetinvaildateQueries를 사용해서 대기열을 관리하는 queryquerykey를 받아와 해당 내용을 무효화시켜 주는 것이었다.

하지만, 이 방법대로 해도 문제가 해결되지 않아서 다른 방법을 추가로 시도해봤다.

두 번째로는 대기 상태 페이지에서 React-Query의 useQuerydata 외에도 refetch 함수도 받아오도록 한 뒤, useEffect를 활용해 그 속에서 1회성으로 refetch를 실행시키는 것이었다.

방법은 아래의 내용을 참고해서 진행해봤다.

React에서 react-query refetch()사용하기.

다만, 이 방법 역시 원하는 대로 작동하지 않았다.

게다가, useEffect로 데이터를 받아오는 방법을 지양하고 대신 useQuery를 응용하려고 노력하고 있었기 때문에 된다고 했어도 이 방법을 사용하진 않았을 것이다.

자, 그럼 마지막으로 시도했던 건 무엇이었을까? 바로, Firebase의 함수인 onSnapShot을 사용해서 데이터를 받아오는 것이었다.

이 방법은 이전에 Firebase를 학습했을 때 써본 적이 있어서 될 것 같다는 확신이 있었는데, 해당 내용을 useQueryqueryFngetDocs의 대체제로 사용해보려고 했다.

그러나 결과는 unsubscribe 형식을 받아온다고 정상적으로 데이터를 받아오지 못해서 이 방법도 해결책이 되지는 않았다.

이 방법을 시도해보고 안되다 보니 공식 문서를 다시 읽고, 공부했었던 내용을 참고하다가 문득 한 가지를 깨달았다.

위의 onSnapShot은 데이터의 상태가 변화되는 것을 구독하면서 갱신하게 해주는데, 이것은 useQuery가 기본적으로 가지는 refetch와 일맥상통하는 부분이 있다.

이 이야기는 즉슨, useQuery 안에 중복된 기능을 하는 onSnapShot을 넣으려고 했던 것이었다. 만걍 useQueryonSnapShot을 사용한다면 아래의 두 가지를 분리해서 생각해봐야 한다.

  • useEffect를 이용해서 onSnapShot을 적용하는 형태
    • useQuery를 사용하지 않고, onSnapShot을 최초 한번 로딩할 때 구독 상태를 적용함으로서 데이터가 변경될 때마다 업데이트되도록 유도한다.
  • useQuery를 이용해서 데이터를 받아오는 형태
    • 원래 지향하고자 하는 방향이고, 이 경우에는 onSnapShot은 중복된 기능으로 쓰지 말고 기존에 사용하던 getDocs를 사용해야 한다.
      • 하고자 하는 방식은 이 형태이지만 정작 계속 문제가 생기고 있다.

ChatGPT에 한 번 물어봤더니 비슷한 내용이 나왔다.

ChatGPT에 한 번 물어봤더니 비슷한 내용이 나왔다.

이런 상황이다보니까 다시 원점으로 돌아와서 어떻게 해야할까.. 이 방법과 관련해서 고민하던 중에 이몬(@infp_dev) 님께서 주신 말씀을 다시 한 번 읽어보게 됐다.

getDocs() 함수에 대해 알아보니 이 함수도 자체적으로 로컬캐시를 가지고있는것 같아요! getDocs의 쿼리를 무효화하는 방향으로 가보심이! (혹은 캐시를 저장하지 않는 옵션이라던지..)

위의 내용대로라면 어찌됐든 getDocs로 받아오는 걸 갱신하기 위해선 query를 무효화 해줘서 내용을 다시 갱신하도록 해줘야하는 건데, 생각한 대로 작성해도 안되니 머리가 아파왔다.

이후 처음부터 다시 천천히 코드를 읽어봤는데 문제가 되는 부분을 발견했다. 처음의 방법에 작성한 invaildQueries에서, 무효화하고자 했던 queryquerykey가 다른 값으로 적혀있었다.

근데 맨 첫 번째로 시도했던 이 방법의 이미지를 보면 정상적으로 querykey를 쓴 것을 볼 수 있는데, 아마 내용과 관련하여 문제 확인을 하다보니 무의식적으로 querykey를 Firestore의 collection name으로 썼나 보다.

이 내용의 수정으로 문제 해결이 됐으면 좋겠지만은, 결국 이 부분은 맨 먼저 시도했던 상태로 돌아간거나 다름없으므로 원점 회귀였는데 이 상황을 타개할 다른 방법이 없을까 했더니 비타 님(@minkyeongJ_dev)이 아래의 말씀을 해주신 내용이 생각났다.

onSubmit할 때 refetch 호출만 해줘도 데이터 불러와질 것 같슴다. 쿼리문 안에 말구용

useQuery에서 refetch 함수를 사용하는 건 시도해봤으나 잘 안됐다.

그렇다면 useQuery에서가 아니라 refetch와 관련한 무언가가 아무튼 존재하지 않을까?

그 사항을 생각해서 검색을 하다보니 아래의 내용을 발견하게 됐다.

React Query의 InvalidateQueries가 동작하지 않을 때

QueryClient | TanStack Query Docs

InvaildQueries 내에도 여러가지 Options가 존재하는데 위의 블로그에서는 v3을 기준으로 작성되어 있으므로 refetchInactive라는 옵션을 true로 활성화하여 적용시켜줬으나 v4에서는 이 옵션이 다른 기능과 합쳐져 refetchtype라는 옵션으로 변경이 되었다.

옵션은 'active', 'inactive', 'all','none' 이렇게 네 가지가 있는데 블로그의 내용을 참고하여 inactive를 배치해 아래와 같이 코드를 작성해봤다.

이 옵션을 붙여보고 이제 되는지 시도해봤더니 본격적으로 데이터 갱신이 잘 된다..!!

이 문제를 해결하는데 머리에 용을 쓰고 2-3일을 굴러다녔는데 해결한 내용이 너무나도 허무한 결과였지만..

조금 더 React-Query의 문서를 들여다보고 여러 옵션을 찾아볼 수 있는 기회가 되기도 했고 querykey에 대해 좀 더 신경써야하는 계기도 줬으니 좋은 삽질을 했던 시기라고 생각하기로 했다.

물론 이 이후로 멘탈이 나가서 한동안 코드 작업에 집중하지 못한 것은 반성해야하지만 말이다.

+2023-03-13 추가

지금 코드에서는 refetchType을 쓰고 있지 않고 있다.

안 써도 잘 작동되기 때문인데.. 그렇담 대체 무엇이 원인이었을까? 어딘가 쿼리 값을 제대로 받아오지 못한 부분이 있었던 걸까?

원인을 알 수가 없어서 미궁에 빠졌다.. 언젠가 깨닫게 되면 별도의 글로 추가 정리해보도록 하겠다.

어쩌다보니 두 번의 커피챗 이야기

이번 주에 기회가 어찌어찌 되어서 두 번의 커피챗을 진행하게 됐다.

한 번은 부트캠프 때부터 인연이 닿았던 탐정토끼(@stelo_kim) 님과의 커피챗을 진행했고, 다른 한 번은 최근 트친이 되셨던 서나무(@seo__namu) 님과의 커피챗을 진행하게 됐다.

탐정토끼 님은 부트캠프 당시에 엄청 많은 조언과 도움을 주신 분이셔서 언젠가 꼭 뵙고 싶었다고 생각했는데, 커피챗 할 사람 찾는다는 트윗을 올렸을 때 (사실 해주실 분을 찾아야 할 입장인데 하자고 해주셔서 너무나도 영광이었다..) 멘션으로 ‘하실래요?’라고 먼저 말씀해주셨다.

개인적으로 커피챗을 할 때는 해주시는 분이 최대한 편하신 장소로 직접 이동한다라는 규칙을 세웠던지라, 탐정토끼 님을 뵈러 갈 때는 경의중앙선인 야당역을 향해 움직였다.

탐정토끼 님과의 커피챗은 굉장히 인상적이었는데, 커피챗을 해보는 게 이번이 두 번째라 어떤 대화를 이어가야할지 잘 몰랐는데 탐정토끼 님께서 이런저런 이야기의 물꼬를 틀어주신 덕분에 굉장히 오랜 이야기를 나눌 수 있었다.

특히 웹 접근성에 관심이 있다보니 이와 관련한 이야기를 많이 나눴고, 이 접근성을 위한 테스트 코드 작성의 필요성에 관해서도 좋은 말씀들을 많이 들었다.

토끼 님의 이야기들은 굉장히 흥미로운 내용들이 많았는데, 커피챗이 끝나고 궁금해했던 사항들에 관해서 혼자서 한 번 더 익혀볼 수 있도록 DM으로 몇 가지 자료를 보내주셨다.

추후 프로젝트가 일단락되면 이렇게 보내주신 자료를 토대로 궁금해했던 사항들을 공부해보고 그 내용을 별도로 정리해볼까 한다.

서나무 님과는 이야기를 많이 나눈 편은 아니지만, 커피챗을 하고 싶다고 말씀해주셔서 일요일에 번개라는 느낌으로 커피챗을 진행하게 됐다.

나무 님께서는 현재 진행중인 프로젝트에 관한 부분, 그리고 기획 관련 프로세스, 그리고 가끔씩 개최하는 ‘모여봐요 너굴의 숲’ 파티 프로젝트 등을 궁금해 하셨다.

이런저런 설명도 드리긴 했지만, 위에 적었듯 워낙 성격이 ‘투 머치 토커’이고 대화를 정말 좋아하고 사람 만나는 걸 좋아하다보니 좀 과하게 이야기를 많이 한 것이 있지 않았나 싶었다.

한편으로는 이런 대화를 나누면서 나무 님께서 궁금하신 것은 없는지 물어보셨는데, 갑작스러운 커피챗이기도 했고 사실 커피챗 관련으로 무슨 대화를 나눌지 항상 고민하지 않다보니까 여쭤보고 싶은 사항들이 없었다.

지금와서 되돌아보면 몇 가지 고민 거리를 이야기했으면 어땠을까 싶기도 하고, 너무 많은 이야기로 인해 사람이 질리진 않았을까 싶어서 죄송하기도 하고.. 아무튼 그렇다.

두 번의 커피챗을 진행하고 난 뒤에, 잠시 커피챗 내용을 이렇게 되짚어보고 나니까 개발자들이 말하는 ‘커피챗’이란 무엇일까라는 생각이 들었다.

나에게 있어서의 커피챗은 취업 고민, 개발 고민 등이 아니더라도 비개발적인 이야기도 좋고, 일상적인 이야기만 주구장창 해도 괜찮은 말 그대로 ‘스몰 토크’를 나누는 자리, 상대방과 얼굴을 마주보며 친목을 다지는 자리라고 생각하고 있는데 주변에서의 커피챗을 보면 위의 고민에 대한 부분들 위주로 커피챗을 진행하는 것 같아서 내가 하는 게 정말 커피챗이 맞는지 혼란스러워졌다.

사실 다양한 사람을 만나고 이야기를 나누면서, 그 사람을 알아가고 친해지고 하면서 이런저런 것들을 많이 알아가고 싶었기에 개발적인 이야기가 아니더라도 토크를 하고 싶어서 커피챗을 하고 싶다고 생각했는데 내가 용어를 오용하는 게 아닌가 싶어서 그 부분이 마음에 걸렸다.

이런 생각이 자꾸 들어서 좀 고민이 되어 트윗을 올렸더니 몇 분께서 자신들도 커피챗을 개발이나 취업 이야기를 하는 데에 국한하고 있지 않으며 즐겁게 아무 이야기를 떠드는 자리로 생각하고 계신다고 했다.

그 말씀을 들으니 너무 커피챗을 하는 것을 부담감을 가지고 있지 않았나 싶은 생각도 들고.. 덕분에 커피챗을 대하는 것에 마음이 조금은 편해졌다.

앞으로도 이런 느낌으로 커피챗을 진행하고 싶은데, 다양한 분들과 이야기를 나누면서 많은 것들을 알아가고 친해져가고 싶다. 물론 저 위에 언급한 개발이나 취업 같은 것도 함께 이런 기회를 통해 성장할 수 있다면 더 좋고 말이다.

다음 주에 할 일들

이번 주에 준비하려고 했던 것들이 제대로 진행되지 못했으므로, 다음 주에 할일들은 이번 주의 업무의 연장선이 될 것 같다.

  • 관리자 페이지 View 구현 진행
  • 폰트, 공용 색상 등 선정
  • 인증 문제 등 발생 시 출력할 Toast 메시지 각 페이지 별로 배치

계속된 집중력 저하로 진도가 너무 안 나가기도 했고, 요즘 점점 체력이 떨어졌다는 걸 실감하기도 하니 아무래도 프로젝트가 어느 정도 끝나면 운동을 좀 해야겠다.

요즘 살이 찌기도 했으니 말이다..

그 밖에 얼마 전에 사장님께서 오랜만에 가게를 맡으셔서 잠시 대화를 나눌 수 있었는데, 프로젝트와 관련해서 카카오의 지원이나 알림 등을 쓸 수 없다는 이야기를 전달드리기도 했고, 가게에서 사장님이 3호점을 준비하시는 데에 집중하고 계시다는 소식을 들었다.

어쩌면 이용처가 하나 더 확장된다는 느낌이니 좀 더 신경써서 만들어야겠구나.. 싶은데 정말 잘 만들어서 가게에도, 나에게도 도움이 되는 친구가 되어줬으면 좋겠다.

참고 자료

Array.prototype.findIndex() - JavaScript | MDN

React에서 react-query refetch()사용하기.

Cloud Firestore로 실시간 업데이트 가져오기  |  Firebase

useQuery | TanStack Query Docs

React Query의 InvalidateQueries가 동작하지 않을 때

QueryClient | TanStack Query Docs