HOC 컴포넌트를 비워놓으면 NULL이 오는가?

sham·2025년 2월 27일

이슈

TableContent를 사용할 때 테이블 칼럼 값을 tbody의 tr들로 뿌려주기 위해 DynamicTableContent를 사용한다.

TableContent에서 데이터 없을 경우의 처리를 위해 삼항연산자를 정의해두고, DynamicTableContent 단에서 데이터가 제대로 안 들어가게 되면 TableContent 단에서 캐치하게끔 의도했는데, 리턴하는 JSX 값이 <></>도, 대놓고 NULL을 리턴해도 TableContent의 props인 tableContent는 비어있다는 것을 인식하지 못하는 이슈가 발생했다.

<ViewTable
	tableHeader={<DynamicTableHeader thItems={thItems} />}
	tableContent={<DynamicTableContent items={[items]} textAligns={textAligns} />}
	hasPage
	hasFilter
/>

interface DynamicTableContentProps<T extends Record<string, CellType>> {
  items: T[];
  textAligns?: string[];
  columnsCallback?: (() => void)[];
  focus?: number;
}

const DynamicTableContent = <T extends Record<string, CellType>>({
  items,
  textAligns,
  columnsCallback,
  focus,
}: DynamicTableContentProps<T>) => {

  if (items.length === 0) return null; // 이래도 비어있다 인식을 못함
  return (
    <>
      {items.length > 0 &&
        items.map((item, index) => (
          <tr
            key={index}
            onClick={columnsCallback && columnsCallback[index]}
            className={index == focus ? 'selected' : ''}
          >
            {Object.keys(item).map((key, keyIndex) => (
              <td key={key} className={textAligns && textAligns[keyIndex]}>
                {item[key]}
              </td>
            ))}
          </tr>
        ))}
    </>
  );
};

export default DynamicTableContent;
import * as S from './styles';

interface TableContentProps {
  tableHeader: React.ReactNode;
  tableContent: React.ReactNode;
}

const TableContent = ({ tableHeader, tableContent }: TableContentProps) => {
  // tableContent 가 비어있어도 삼항 연산자가 인식을 하지 못함
  return (
    <S.TableRoot>
      <S.TableHeader>{tableHeader}</S.TableHeader>
      {tableContent ? (
        <S.TableBody>{tableContent}</S.TableBody>
      ) : (
        <S.Empty>
          <div>리스트가 없습니다</div>
        </S.Empty>
      )}
    </S.TableRoot>
  );
};

export default TableContent;

해결

null을 반환하는 컴포넌트도 React Element이다

React에서는 null을 반환하는 JSX 코드가 있어도 내부적으로 React.createElement() 가 호출된다.

즉, JSX는 항상 React 요소(ReactElement)로 변환되며, 이는 $$typeof: Symbol(react.element) 속성을 가지게 된다.

null을 반환하는 children 격의 컴포넌트를 콘솔로 찍으면 다음과 같은 값이 나온다.

{
  "$$typeof": "Symbol(react.element)",
  "key": null,
  "props": {
    "items": null,
    "textAligns": Array(9),
  },
  "ref": null,
  "type": "({ items, textAligns, columnsCallback, focus }) => {…}",
  "_owner": "FiberNode",
  "_store": {
    "validated": true
  },
  "_self": undefined,
  "_source": {
    "fileName": "C:/Users/qwerg/Desktop/work/wiseon_master_admin/src/pages/ManagementUpdatePage/index.tsx",
    "lineNumber": 42,
    "columnNumber": 25
  }
}

HOC를 사용하는 단에서 검증은 피하자

TableContent에서 하던 삼항 연산 분기를 TableContent가 사용할 element를 jsx로 리턴해주는 DynamicTableContent 컴포넌트 단에서 진행하도록 수정해주었다. 이렇게 되면 분기를 통해 최종적으로 렌더링되야 할 요소들이 의도했던 대로 TableContent에 전달되어진다.


interface DynamicTableContentProps<T extends Record<string, CellType>> {
  items: T[];
  textAligns?: string[];
  columnsCallback?: (() => void)[];
  focus?: number;
}

const DynamicTableContent = <T extends Record<string, CellType>>({
  items,
  textAligns,
  columnsCallback,
  focus,
}: DynamicTableContentProps<T>) => {

  if (items.length === 0) return null; // 이래도 비어있다 인식을 못함
  return (
    <>
      {items.length > 0 &&
        items.map((item, index) => (
          <tr
            key={index}
            onClick={columnsCallback && columnsCallback[index]}
            className={index == focus ? 'selected' : ''}
          >
            {Object.keys(item).map((key, keyIndex) => (
              <td key={key} className={textAligns && textAligns[keyIndex]}>
                {item[key]}
              </td>
            ))}
          </tr>
        ))}
    </>
  );
};

export default DynamicTableContent;
import * as S from './styles';

interface DynamicTableContentProps<T extends Record<string, CellType>> {
  items: T[];
  textAligns?: string[];
  columnsCallback?: (() => void)[];
  focus?: number;
}

const DynamicTableContent = <T extends Record<string, CellType>>({
  items,
  textAligns,
  columnsCallback,
  focus,
}: DynamicTableContentProps<T>) => {
  return (
    <>
      {items && items.length > 0 ? (
        <S.TableBody>
          {items.map((item, index) => (
            <tr
              key={index}
              onClick={columnsCallback && columnsCallback[index]}
              className={index == focus ? 'selected' : ''}
            >
              {Object.keys(item).map((key, keyIndex) => (
                <td key={key} className={textAligns && textAligns[keyIndex]}>
                  {item[key]}
                </td>
              ))}
            </tr>
          ))}
        </S.TableBody>
      ) : (
        <S.Empty>
          <div>리스트가 없습니다</div>
        </S.Empty>
      )}
    </>
  );
};

export default DynamicTableContent;
profile
씨앗 개발자

0개의 댓글