조건부 렌더링을 통한 컴포넌트 재사용

HYUK·2023년 5월 11일
0

리액트

목록 보기
1/1

1. 이슈내용

아래 이미지 처럼 하나의 컴포넌트로 총 3개의 개시물을 출력해야되는 상황이였고 data가 없는경우 아래 이미지처럼 출력이되고 data가 있는 경우 해당개시물의 썸내일이 출력이되게 되도록 구현하였다.

2. 해결과정

우선 조건부 랜더링을 진행하기 위해서 삼항연산자를 사용해야될거라는 생각을 하였다. 그래서 적용한 코드는 아래와 같다.

{MY_PAGE_CATEGORY.map(({ title, id }) => {
            const postingData =
              title === '내포스팅'
                ? data[0].myPosting
                : title === '스크랩'
                ? data[0].scrape
                : title === '좋아요'
                ? data[0].like
                : [];
            return (
              <>
                <S.PostingTitleWrap>
                  <S.PostingTitle key={id}>{title}</S.PostingTitle>
                  <S.CountPosting>{postingData.length}</S.CountPosting>
                </S.PostingTitleWrap>
                <ListComponent data={postingData} />
              </>
            );
          })}

이러한식으로 적용하였는데 에러가 발생하였다 에러내용은

<S.CountPosting>{postingData.length}</S.CountPosting> 

이 부분에서

Cannot read properties of undefined (reading 'length')

위와같은 에러가 발생하여 발생하는 이유를 찾아본결과

Cannot read properties of undefined (reading 'length') 에러는 postingData 변수가 undefined인 경우에 length 속성을 읽으려고 할 때 발생한다. 이 에러는 postingData가 초기에 선언되지 않거나 data 배열에 해당하는 데이터가 없어서 postingData가 undefined인 경우에 발생할 수 있다.

그래서 변경해준내용은 아래와 같았고 의도한대로 잘 출력이 되었다.

 {MY_PAGE_CATEGORY.map(({ title, id }) => {
        const postingData =
          title === '내포스팅'
            ? data[0]?.myPosting || [] // undefined인 경우 빈 배열로 설정
            : title === '스크랩'
            ? data[0]?.scrape || [] // undefined인 경우 빈 배열로 설정
            : title === '좋아요'
            ? data[0]?.like || [] // undefined인 경우 빈 배열로 설정
            : [];

        return (
          <>
            <S.PostingTitleWrap>
              <S.PostingTitle key={id}>{title}</S.PostingTitle>
              <S.CountPosting>
                {postingData && postingData.length} {/* undefined인 경우 표시하지 않음 */}
              </S.CountPosting>
            </S.PostingTitleWrap>
            <ListComponent data={postingData} />
          </>
        );
      })}

이렇게 각각에 해당하는 개시물들이 위치에 맞게 렌더링은 잘 되었으나 빈 data의 경우에 랜더링 되는 안내 text가 모든 영역에서 동일하게 출력이 되고있었다. 여기서 다시또 생각해낸것이 MY_PAGE_CATEGORY이 상수데이터의 title과 EMPTY_LIST_INFORM의 index를 사용하였고 특정개시물이 렌더링될때 특정 index의 상수데이터를 출력하게 코드를 작성하였다.

//ListComponent.tsx

let emptyInfo = null;

  if (data && data.length > 0) {
    if (data[0]?.myPosting?.length === 0) {
      emptyInfo = EMPTY_LIST_INFORM[0];
    } else if (data[0]?.like?.length === 0) {
      emptyInfo = EMPTY_LIST_INFORM[1];
    } else if (data[0]?.scrape?.length === 0) {
      emptyInfo = EMPTY_LIST_INFORM[2];
    }
  }
  
  
  return (
    <S.MyPosting>
      {emptyInfo ? (
        <Link to="/posting">
          <S.EmptyWrap>
            <S.EmptyList src={emptyInfo.emptyImg} alt="emptyList" />
            <S.AddPosting>{emptyInfo.emptyText}</S.AddPosting>
          </S.EmptyWrap>
        </Link>
      ) : (
        <S.ThumbnailWrapper>
          {data.map((posting: string, id: number) => (
            <S.Thumbnail key={id} src={posting} alt="Thumbnail" />
          ))}
        </S.ThumbnailWrapper>
      )}
    </S.MyPosting>
  );
}

위와 같은방법으로 작성하였는데 이번에는 EMPTY_LIST_INFORM의 데이터가 아예 출력이되지 않았다. 다른 여러가지 방법을 시도했는데도 해결이되지 않아 코드 설계를 조금 바꿔서 적용해보기로 하였다.

우선, EMPTY_LIST_INFORM을 상위 컴포넌트인 Mypage.tsx에 작성을 해주었고 getEmptyListInfo라는 함수를 새롭게 만들어주었는데 적용된 고드내용은 아래와같다.

//Mypage.tsx

 const getEmptyListInfo = (title: string) => {
    const emptyInfo = EMPTY_LIST_INFORM.find(
      info =>
        info.id ===
        MY_PAGE_CATEGORY.find(category => category.title === title)?.id
    );
    return emptyInfo || EMPTY_LIST_INFORM[0];
  };

return(
  
	......
  
  		<S.PostingWrap>
          {MY_PAGE_CATEGORY.map(({ title, id }) => {
            const postingData =
              title === '내포스팅'
                ? data[0]?.myPosting || [] // undefined인 경우 빈 배열로 설정
                : title === '스크랩'
                ? data[0]?.scrape || [] // undefined인 경우 빈 배열로 설정
                : title === '좋아요'
                ? data[0]?.like || [] // undefined인 경우 빈 배열로 설정
                : [];
            const emptyInfo = getEmptyListInfo(title);

            return (
              <>
                <S.PostingTitleWrap>
                  <S.PostingTitle key={id}>{title}</S.PostingTitle>
                  <S.CountPosting>{postingData.length}</S.CountPosting>
                </S.PostingTitleWrap>
                <ListComponent data={postingData} emptyInfo={emptyInfo} />
              </>
            );
          })}
        </S.PostingWrap>

	......
)

먼저 위에서 가장 핵심이되는 내용은 getEmptyListInfo함수인데 설명하자면 아래와 같다.

  1. getEmptyListInfo 함수는 title이라는 문자열 매개변수를 받는다.
  2. EMPTY_LIST_INFORM 배열에서 emptyInfo라는 변수를 선언한다. EMPTY_LIST_INFORM 배열은 빈 목록에 대한 정보를 담고 있는 객체들의 배열이다.
  3. EMPTY_LIST_INFORM 배열에서 find 함수를 사용하여 info 객체를 찾는다. 이 때, info 객체의 id 값이 다음 조건을 만족해야 한다: MY_PAGE_CATEGORY 배열에서 category 객체를 찾고, 그 객체의 title 값이 함수의 매개변수인 title과 동일해야 한다.
  4. MY_PAGE_CATEGORY 배열은 카테고리에 대한 정보를 담고 있는 객체들의 배열이다.
  5. find 함수는 조건을 만족하는 첫 번째 객체를 반환하거나, 조건을 만족하는 객체가 없을 경우 undefined를 반환한다.
  6. emptyInfo가 undefined이면, EMPTY_LIST_INFORM 배열의 첫 번째 객체를 선택하여 반환한다. 그렇지 않으면, emptyInfo 객체를 반환한다.
//ListComponent.tsx

interface EmptyInfo {
  id: number;
  emptyImg: string;
  emptyText: string;
}

interface ListComponentProps {
  data: string[];
  emptyInfo: EmptyInfo;
}

export default function ListComponent({ data, emptyInfo }: ListComponentProps) {
  return (
    <S.MyPosting>
      {data.length === 0 ? (
        <Link to="/posting">
          <S.EmptyWrap>
            <S.EmptyList src={emptyInfo.emptyImg} alt="emptyList" />
            <S.AddPosting>{emptyInfo.emptyText}</S.AddPosting>
          </S.EmptyWrap>
        </Link>
      ) : (
        <S.ThumbnailWrapper>
          {data.map((posting: string, id: number) => (
            <S.Thumbnail key={id} src={posting} alt="Thumbnail" />
          ))}
        </S.ThumbnailWrapper>
      )}
    </S.MyPosting>
  );
}

위와같이 작성하여 의도한대로 모든내용이 잘 나왔다 그런데 하나 눈에 거슬렸던게 EMPTY_LIST_INFORM의 text내용을 분명 상수데이터에서 '\n'을 이용하여 개행처리를 해주었는데도 계속해서 적용이 되지않았다. 개발자도구창에서는 개행처리가 되는걸로는 나오는데 작은게 너무나 거슬려서 그냥넘어갈 수 없었고, 그래서 무슨이유인지 찾아보았는데 방법은 아주 간단했다. 일단 '\n' 이렇게 상수데이터에 개행처리를 해준뒤 적용되는 태그에 white-space: pre-wrap; 이렇게 스타일을 적용해주면 아주쉽게 개행처리가 될 수 있었다.

profile
step by step

0개의 댓글