react - 조건부 렌더링: useEffect()에서 fetch시 갑자기 빈배열이 출력되는 이유

신혜린·2023년 3월 2일
0

wecode42

목록 보기
31/32

레이아웃 제작을 완료하고 기능 구현을 하기 위해 mock data를 생성해서 useEffect() 안에 fetch()함수를 사용하여 데이터를 불러오던 도중, 처음에는 데이터가 잘 담겨서 불러와지다가 갑자기 빈배열이 출력되는 현상이 발생했다. 뭔가 useEffect와 관련이 있을 것 같아서 정확히 정리하기 위해 공부하면서 벨로그 글을 작성해보기로!

문제상황

const [thumbnail, setThumbnail] = useState([]);

useEffect(() => {
    fetch("/data/Detail/getDetail.json")
      .then(res => res.json())
      .then(data => setThumbnail(data));
    console.log(thumbnail);
  }, []); 

위 코드는 내가 생성한 mock data getDetail.json 안의 데이터를 thumbnail 안에 담아 state 상태로 관리하기 위해 짠 코드 내역이다. 처음에는 getDetail.json 안의 데이터가 잘 출력이 되다가 어느 순간부터 [] 빈배열로 출력되는 것이다..! 무엇이 문제인 것일까 🤔

동기적 vs 비동기적

  • 동기적(Synchronous)
    어떤 작업을 요청했을 때 그 작업이 종료될때 까지 기다린 후 다음 작업을 수행하는 방식

  • 비동기적(Asynchronous)
    어떤 작업을 요청했을 때 그 작업이 종료될때 까지 기다리지 않고 다른 작업을 하고 있다가, 요청했던 작업이 종료되면 그에 대한 추가 작업을 수행하는 방식

setThumbnail 함수는 비동기적으로 처리된다. 즉, 종료되는 순서대로 처리되는 게 아니라 setThumbnail 함수가 완료되기 전에 console.log(thumbnail) 구문이 먼저 실행될 수도 있다는 뜻이다.


console.log(thumbnail) 구문이 실행되는 시점에는 thumbnail 상태(state)가 아직 업데이트되지 않았을 가능성이 있기 때문에 빈 배열 ([]) 이 출력되는 것이다.

React에러: Cannot read properties of undefined ...

"data" : {
  "detailImages" : {
    "detailImageLectureId": 1
  }
}

이런 식의 구조로 되어있는 json 파일 안에 detailImages 데이터를 따로 변수에 담아 관리하기 위해 const thumbnailData = thumbnail.data.detailImages 라는 코드를 작성한 상태에서 console.log을 찍으니 저런 타입 에러가 났다.
찾아보니 이는 위에서 말한 비동기적 처리와 관련이 있는 거 같았는데, state 처리 자체가 비동기적이기 때문에 렌더링이 되기도 전에 동작이 되어서 state의 상태가 정의되기 전 (빈 배열) 상태이기 때문에 undefined 에러가 뜬다는 것이다.
즉, 내가 담은 thumbnail 상태가 어느 순간부터 빈배열이 되어 데이터 타입이 undefined 가 되었기 때문에 원하는 대로 thumbnailData 변수에 담기지 않는 것이다.

해결방법 - 조건부 렌더링

위의 에러를 해결하기 위해서는 삼항연산자 혹은 && 연산자를 이용하여 조건부 렌더링을 구현해볼 수 있다.

  • 삼항연산자 : 조건의 true/false에 따라 각기 다른 UI 를 렌더링 할 때 사용
  • && 연산자 : 조건이 true일 때만 특정 UI를 렌더링하고, false일 때는 아무것도 렌더링하지 않을 때 사용

첫 시도

  return (
    <ThumbnailLayout>
      {thumbnail.data.detailImages && (
        <>
          <HighlightLayout>
            <HighlightThumbnail
              key={thumbnail.data.detailImages[0]?.detailImageLectureId}
              alt="thumbnail"
              src={thumbnail.data.detailImages[0]?.detailImageUrl}
            />
          </HighlightLayout>
          <AdditionalLayout>
            <AdditionalThumbnail
              key={thumbnail.data.detailImages[1]?.detailImageLectureId}
              alt="thumbnail"
              src={thumbnail.data.detailImages[1]?.detailImageUrl}
            />
            <AdditionalThumbnail
              key={thumbnail.data.detailImages[2]?.detailImageLectureId}
              alt="thumbnail"
              src={thumbnail.data.detailImages[2]?.detailImageUrl}
            />
            <ThumbnailButton>+2개의 이미지</ThumbnailButton>
          </AdditionalLayout>
        </>
      )}
    </ThumbnailLayout>
  );
};

렌더링하고자 하는 태그 안에 && 연산자를 이용하여 조건부 렌더링 코드를 짰는데도 안된다!
그 이유는 ... thumbnail.data.detailImages 데이터 자체가 배열 형태인데 javascripts는 빈 배열, 빈 객체도 true로 인식하기 때문에 state가 undefined여도 UI가 출력이 되는 것이다.(ㅠㅠ)

  • truthy(참 같은 값)이여서 true로 인식하는 데이터 예시들
      if (true)
       if ({})
       if ([])
       if (42)
       if ("0")
       if ("false")
       if (new Date())
       if (-42)
       if (12n)
       if (3.14)
       if (-3.14)
       if (Infinity)
       if (-Infinity)

두 번째 시도

const [thumbnail, setThumbnail] = useState({});

  useEffect(() => {
    fetch("/data/Detail/getDetail.json")
      .then(res => res.json())
      .then(data => setThumbnail(data.data));
  }, []);
  console.log(thumbnail);

  return (
    <ThumbnailLayout>
      {thumbnail.lectureId && (
        <>
          <HighlightLayout>
            <HighlightThumbnail
              key={thumbnail.detailImages[0]?.detailImageLectureId}
              alt="thumbnail"
              src={thumbnail.detailImages[0]?.detailImageUrl}
            />
          </HighlightLayout>
          <AdditionalLayout>
            <AdditionalThumbnail
              key={thumbnail.detailImages[1]?.detailImageLectureId}
              alt="thumbnail"
              src={thumbnail.detailImages[1]?.detailImageUrl}
            />
            <AdditionalThumbnail
              key={thumbnail.detailImages[2]?.detailImageLectureId}
              alt="thumbnail"
              src={thumbnail.detailImages[2]?.detailImageUrl}
            />
            <ThumbnailButton>+2개의 이미지</ThumbnailButton>
          </AdditionalLayout>
        </>
      )}
    </ThumbnailLayout>
  );
.then(data => setThumbnail(data.data));

데이터를 불러올 때 data 대신 data.data로 불러와서 depth를 줄여줬다.

{thumbnail.lectureId && ()}
 if (!thumbnail.lectureId) return null // 위와 같은 의미

thumbnail.lectureId 값이 false일 때 null을 반환하라는 의미! lectureId 값은 배열이 아니기 때문에 json 값이 state에 담기기 전까지는 false로 인식한다.

이렇게 코드를 바꿔주니까 드 디 어 정상적으로 작동하는 모습을 확인할 수 있었다!!!


💭 단순히 그냥 담기만 하면 될 것 같은데도 이렇게 복잡한 과정을 거쳐야 한다니.. 증말로 어렵구만 🤔 그래도 이렇게 하나하나 해결해 나아가고 느리더라도 천천히 이유를 이해하는 과정이 짜릿한 법이니까~

profile
개 발자국 🐾

0개의 댓글