yamoo9 / likelion-FEQA

질문/답변 — 프론트엔드 스쿨, 멋사
29 stars 9 forks source link

[LAB-4] 파이어스토어 -query에서 where를 이용하기 #264

Closed SeoMiYoung closed 1 year ago

SeoMiYoung commented 1 year ago

질문 작성자

서미영

문제 상황

안녕하세요. 저는 현재 마켓칼리에서 상품문의 파트를 맡아서 진행하고 있습니다. 사용자들이 특정 상품 디테일 페이지에 접속해서, 상품문의글을 쓸때마다 파이어스토어에 데이터가 쌓이게 됩니다. 현재 구현한 상황은, 파이어스토어에 데이터들이 쌓이고, 모~든 문의글이 어떤 상품페이지이든 보이게끔 구현했습니다.

그러나, 제가 원하는 건, 예를 들어 '탱탱쫄면'페이지에 들어가면, '탱탱쫄면'에 대한 문의글만 보이게 하고, '유보호 만두'페이지에 들어가면, '유부호 만두'에 해당하는 문의글만 보이게 하고 싶습니다.

사실 제가 파이어베이스, 파이어스토어에 대해서 잘 알고 있지 않아서 인터넷 검색에 의존할 수 밖에 없었는데, 인터넷을 통해 찾아보니, where조건문을 줌으로써 조건을 걸어줄 수 있다고 합니다.

그래서 저는 컬렉션안의 문서들을 [ {..}, {..}, {..}, {..} ] 이런 형태로 모아줘서 return { dataState }반환시켜주는 파일인, useDetailCollection.js에 where를 추가했습니다.

image productIdState안에 현재 보고 있는 페이지의 상품 아이디가 담기도록 했습니다. 상품 아이디는, npm start로 서버에 들어간 후, 화면에서 각각의 상품을 클릭하면, image 상품이미지 위쪽에 보면 현재, product-ekfk 라고 써있는데, 이게 '탱탱쫄면'의 상품ID입니다. 이걸 저는,

image 이런식으로 productId도 함께 파이어스토어에 들어가도록 관리하고 있기 때문에

useDetailCollection.jsconst sortCollectionRecentDate = query(collection(db, collectionName), where("productId", "==", productIdState), orderBy("date", "desc")); 이런식으로 코드를 짰습니다. 위의 코드에서 where("productId", "==", productIdState),를 빼면 상품에 상관없이 모든 문의글이 화면에 보이게 됩니다. 저는 그래서, 현재 어떤 상품페이지에 있는지 상품 id정보가 담긴 변수 productIdState와, 파이어스토어에 들어간 productId값이 같은것만 출력되길 원해서 where를 위와같이 짰습니다.

그러나 where를 넣자, 아무글도 출력되지 않았습니다. 그래서 문제를 찾아보려고 개발자 도구를 열어보자, image 위와 같은 경고문구가 떴고, 구글링 해본 결과, orderBy와 where를 모두 함께 썼기 때문에 index(색인)이 필요할 수도 있겠다는 생각이 들어서, image 위와 같이 색인을 추가했으나 잘 작동되질 않습니다.

질문1)

제가 작성한 where이 틀린 것일까요? 왜 원하는 결과가 나오지 않았던 걸까요?

질문2)

useDetailCollection.js 이 파일은 파이어스토어에 문서가 3개가 있다면, { title: '제목1', textarea: '내용1' } { title: '제목2', textarea: '내용2' } { title: '제목3', textarea: '내용3' } 위의 문서들을 [ { title: '제목1', textarea: '내용1' }, { title: '제목2', textarea: '내용2' }, { title: '제목3', textarea: '내용3' } ] 이렇게 모아줘서 dataState에 저장하고, 그 배열을 반환해주는 역할을 합니다.

image 저는 그 파일에서 배열을 잘 받아오고 있는지 확인해보려고 콘솔로 찍어보기로 했습니다. 근데 분명 파이어스토어에 문서들을 저장하려면 이 과정을 거쳐야 하기 때문에, 배열이 잘 저장된 것 같았지만, image 위의 사진을 보시면 현재 배열 상태가 undefined라는 걸 확인할 수 있었습니다. 그래서 드는 궁금증이 일시적으로 잠깐 데이터가 반영이 안되는건가..하는 궁금증에 질문드립니다.

질문2와 비슷한 맥락의 상황

image 위으 사진은 useDetailCollection.js에서 끝부분인데요, 1번에서 [ {..}, {..}, {..} ] 이런형태의 배열을 dataArr에 담아서 그걸 2번에서 배열의 state를 저장하는 dataState에 저장해서, 그걸 3번에서 반환하고 있거든요..

사실상 dataArr와 dataState는 같은 값을 가지고 있어야 맞는데, 콘솔을 보면, image dataArr에는 잘 배열이 들어와서 찍히는데, dataState는 undefined로 그렇지 않은 걸 확인할 수 있었습니다. 왜 그럴까요? ㅠㅠ매우 궁금합니다.

프로젝트 저장소 URL

https://github.com/SeoMiYoung/project-repo branch: feat/https://github.com/yamoo9/likelion-FEQA/issues/96 상품문의에 접근 방법: npm start => 상품을 누른다(탱탱쫄면)

환경 정보

운영체제 정보: 윈도우 Node.js 정보: v18.14.0

yamoo9 commented 1 year ago

문제 해결

요청된 문제가 해결된 화면은 아래 영상에서 확인할 수 있습니다.

가이드 파일 다운로드 → src.zip

https://user-images.githubusercontent.com/1850554/228119208-6ee454d4-cd04-4042-8108-2ec67a4cbbde.mp4

답변 1.

useDetailCollection 훅 코드를 수정해 문제를 해결합니다. (주석 참고) 아래 수정된 코드를 사용하면 "제품 상세 페이지 새로고침 시, 상품 문의를 불러오지 못했던 문제"가 해결됩니다.

export const useDetailCollection = (collectionName) => {
  const [dataState, setDataState] = useState();
  const { productId } = useParams();
  const product = useRecoilValue(productListFamily(productId));
  const [productIdState, setProductIdState] = useRecoilState(productIdAtom);

  // 상품 ID가 변경되면 productIdState 아톰 업데이트
  useEffect(() => {
    setProductIdState(product.id);
  }, [product.id]);

  // productIdState 아톰이 업데이트 되면 이펙트 콜백
  useEffect(() => {
    if (productIdState) {
      const sortCollectionRecentDate = query(
        collection(db, collectionName),
        where('productId', '==', productIdState)
        // Firestore 색인(index) 생성에 문제가 있는 지 정렬 동시 사용에 오류 발생
        // orderBy('date', 'desc')
      );

      const unsub = onSnapshot(sortCollectionRecentDate, (snapshot) => {
        let dataArr = [];

        snapshot.docs.forEach((doc) => {
          dataArr.push({ ...doc.data() });
        });

        // Front-end 개발 단에서 내림차순 정렬 처리
        dataArr.sort(({ date: aDate }, { date: bDate }) => {
          // 오름차순 정렬
          // return aDate.toDate() - bDate.toDate();

          // 내림차순 정렬
          return bDate.toDate() - aDate.toDate();
        });

        setDataState(dataArr);
      });

      return unsub;
    }
  }, [productIdState]);

  return { dataState };
};

ProductDetail 컴폰너트 코드 수정해 페이지 전환 시 스크롤 위치가 변경되지 않는 문제를 해결합니다.

function ProductDetail() {
  const { productId } = useParams();
  const product = useRecoilValue(productListFamily(productId));

  // 페이지 전환 시, 화면 상단으로 스크롤 위치 조정
  useEffect(() => {
    window.scrollTo(0, 0);
  }, []);

  const [productName, setProductName] = useRecoilState(productNameAtom);

  useEffect(() => {
    setProductName(product.name);
  }, []);

  return (
    <div className={styles.ProductDetailWrapper}>
      <h2>ProductDetail {productId}</h2>
      <Link to="/">Go to Home</Link>
      <ProductThumbnail product={product} />
      <ProductReview />
      <ProductInquiry />
      <ProductDetailPopUpLayout />
    </div>
  );
}

답변 2

React 상태 업데이트 이슈 답변 글에 기술한데로 핵심은 "React 상태 업데이트는 즉시 이뤄지지 않습니다" 입니다. 그러므로 상태 업데이트 감지가 될 때 이펙트 콜백 함수를 사용해 결과를 확인해야 합니다.

export const useDetailCollection = (collectionName) => {
  // ...

  // 상품 ID가 변경되면 productIdState 아톰 업데이트
  useEffect(() => {
    // ⛔️ 상태 업데이트는 즉시 일어나지 않습니다.
    setProductIdState(product.id);
  }, [product.id]);

  // productIdState 아톰이 업데이트 되면 이펙트 콜백
  useEffect(() => {
    if (productIdState) {
      // ...

      const unsub = onSnapshot(sortCollectionRecentDate, (snapshot) => {
        let dataArr = [];

        snapshot.docs.forEach((doc) => {
          dataArr.push({ ...doc.data() });
        });

        // ⛔️ 상태 업데이트는 즉시 일어나지 않습니다.
        setDataState(dataArr);
      });

      return unsub;
    }
  }, [productIdState]);

  // ✅ dataState 상태가 변경되면 이펙트 콜백
  useEffect(() => {
    // dateState 상태 값을 출력합니다.
    console.log({ dataState });
  }, [dataState]);

  return { dataState };
};
SeoMiYoung commented 1 year ago

야무쌤 정말 답변 감사합니다! 답변해주신 내용을 바탕으로 학습차원에서 추가 질문이 생겼습니다.

질문1

원래는 index로 진행했을때 잘 적용되야하는게 맞는걸까요?? 많은 분들이 query문에서 orderBy와 where같은걸 사용해서 정렬한 것 같던데, 우선은 index가 잘 안되는것같아서 sort를 구현해 정렬했지만, 원래는 index로 접근하는게 맞는건가요?

질문2

"상태 업데이트 감지가 될 때 이펙트 콜백 함수를 사용해 결과를 확인해야 합니다."라고 하셨는데, 다른 함수들은 사용하면 안되고 오직 이펙트 콜백함수여야 하나요?? 이펙트 콜백함수는 이펙트 효과의 동작이 완료된 이후에 실행된다고 알고있는데, 무슨 느낌인지는 알겠는데 왜 굳이 effect콜백함수에서 해야하나 라는 생각이 들어서 질문합니다. useEffect나 useLaoutEffect같은건 랜더링 된 이후에 실행되서 그런건가요?

yamoo9 commented 1 year ago

야무쌤 정말 답변 감사합니다! 답변해주신 내용을 바탕으로 학습차원에서 추가 질문이 생겼습니다.

질문1 원래는 index로 진행했을때 잘 적용되야하는게 맞는걸까요?? 많은 분들이 query문에서 orderBy와 where같은걸 사용해서 정렬한 것 같던데, 우선은 index가 잘 안되는것같아서 sort를 구현해 정렬했지만, 원래는 index로 접근하는게 맞는건가요?

질문2 "상태 업데이트 감지가 될 때 이펙트 콜백 함수를 사용해 결과를 확인해야 합니다."라고 하셨는데, 다른 함수들은 사용하면 안되고 오직 이펙트 콜백함수여야 하나요?? 이펙트 콜백함수는 이펙트 효과의 동작이 완료된 이후에 실행된다고 알고있는데, 무슨 느낌인지는 알겠는데 왜 굳이 effect콜백함수에서 해야하나 라는 생각이 들어서 질문합니다. useEffect나 useLaoutEffect같은건 랜더링 된 이후에 실행되서 그런건가요?

추가 답변 1.

서버에 요청 한 번으로 원하는 데이터를 가져오는 것이 효율 적이긴 해요. 데이터를 가져온 후, 클라이언트 환경에서 변경하는 데는 시간이 소요되니까요.

추가 답변 2.

"왜? 굳이? useEffect 훅을 사용해 상태 변경을 감지하나?" 질문 하셨지만, React 훅은 그렇게 사용하도록 설계되었습니다.

학교 종은 왜 굳이 자동으로 50분, 10분 주기로 울리나요? → 종이 울리는 주기(조건)만 설정하면 자동으로 처리 함. (React 이펙트 훅)

useEffect 또는 useLayoutEffect에 설정된 함수(이펙트 콜백)가 실행 됨은 종속성 배열에 추가된 상태 또는 Props가 업데이트 됨을 보장합니다.

function Component() {
  const [state, setState] = useState(false);

  useEffect(() => {
    // React는 state가 업데이트 되면 반드시 이 함수를 실행합니다.
    // 이 함수가 실행 되었다는 것은 state가 업데이트 됨을 보장합니다.
    console.log(state);
  }, [state]);

  // ...
}

"다른 함수를 사용하면 안되나?" 질문 주셨는데, 결론은 안됩니다.

자동으로 울리는 학교 종 대신, 선생님이 50분, 10분 주기로 시간 체크해서 말해주면 안되나요? → 수시로 시간을 확인한 후, 때가 되면 직접 종을 울려야 함. (일반 함수)

일반 함수는 호출되어야 실행이 되는데, 상태가 변경 됨을 어떻게 감지할까요? 일반 함수는 이벤트 호출에 의한 경우 또는 목적에 따라 호출된 경우 외에는 스스로 실행되지 않죠. 다시 물어볼께요. 상태가 바뀜을 어떻게 알 수 있죠?

function Component() {
  const [state, setState] = useState(false);

  const myFunc = () => {
    // React는 state가 바뀌어도 이 함수는 호출하지 않아요.
    // 즉, 이 함수는 호출되기 전까지 전혀 실행되지 않습니다.
    console.log(state);
  };

  // ...
}
SeoMiYoung commented 1 year ago

와...!!! 엄청나게 깨달음을 얻었습니다!!! 와!! 정말 너무 감사합니다 ㅠㅠ