발생한 오류 🔥
Type error: Property 'props' does not exist on type 'string | number | bigint | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<...>'. Property 'props' does not exist on type 'string'.
공통 헤더를 만드는 과정에서 `leftIcon` props 속성에 접근할 때 위 오류가 발생했습니다.
'use client';
import { cn } from '@src/utils';
import { useRouter } from 'next/navigation';
import React from 'react';
interface HeaderProps {
leftIcon: React.ReactNode;
title: string;
rightIcon: React.ReactNode;
className: string;
}
const Header = ({ leftIcon, title, rightIcon, className }: Partial<HeaderProps>) => {
const router = useRouter();
const onClickLeftIcon = () => {
const isArrow = leftIcon?.props['aria-label'] === 'arrow'; // 🔥 여기서 오류 발생
if (isArrow) {
router.back();
} else {
router.push('/');
}
};
return (
<header className={cn('flex w-full items-center justify-between bg-white p-4', className)}>
{/* 왼쪽 아이콘 */}
<div className="flex h-6 w-6 items-center">
{leftIcon ? <button onClick={onClickLeftIcon}>{leftIcon}</button> : <div className="w-6" />}
</div>
{/* 중앙 텍스트 */}
<div>{title ? <span className="font-semiBold text-gray-900 font-body-1">{title}</span> : <div />}</div>
{/* 오른쪽 아이콘 */}
<div className="flex h-6 w-6 items-center">
{rightIcon ? <button>{rightIcon}</button> : <div className="w-6" />}
</div>
</header>
);
};
export default React.memo(Header);
해결 과정 🔎
원인 분석
`leftIcon`의 타입이 ReactNode인데 Typescript가 `leftIcon`을 `string | number | ReactElement | ...`로 인식했고 string과 같은 타입에는 `props` 속성이 존재하지 않기 때문에 발생한 오류였습니다.
❓ReactNode와 ReactElement의 차이
`ReactNode`와 `ReactElement`는 React에서 유사해 보이지만 실제로는 다른 개념을 나타냅니다.
ReactNode
- React에서 렌더링 가능한 모든 값을 포함합니다.
- string, number, ReactElement, null, undefined, boolean, 그리고 이들의 배열도 포함됩니다.
ReactElement
- React에서 실제로 컴포넌트를 표현하는 객체입니다. JSX로 작성된 컴포넌트는 항상 ReactElement 타입입니다.
- 예: <div /> 또는 <CustomComponent />
❗따라서 `ReactNode`는 `ReactElement`보다 더 포괄적인 타입이며, `props` 속성은 `ReactElement`에만 존재합니다.
해결 방법 ✨
`React.isValidElement`를 사용해 `ReactNode`가 실제로 `ReactElement`인지 확인 후 접근하는 방법을 선택했습니다.
수정 전 코드
const onClickLeftIcon = () => {
const isArrow = leftIcon?.props['aria-label'] === 'arrow';
if (isArrow) {
router.back();
} else {
router.push('/');
}
};
수정 후 코드
const onClickLeftIcon = () => {
const isArrow = React.isValidElement(leftIcon) && leftIcon.props['aria-label'] === 'arrow';
if (isArrow) {
router.back();
} else {
router.push('/');
}
};
`React.isValidElement`가 런타임에서 타입 가드 역할을 수행하여, `props` 속성에 안전하게 접근할 수 있도록 보장해줍니다.
✨ 코드 설명
1. React.isValidElement(leftIcon)
- leftIcon이 ReactElement인지 확인 후 Typescript는 leftIcon이 ReactElement임을 추론합니다.
2. leftIcon.props['aria-label']
- React.isValidElement를 통해 타입이 좁혀져서 props 속성에 접근해도 오류가 발생하지 않습니다.
회고 🧐
React와 Typescript가 제공하는 타입에 대해서 공부할 수 있었고 타입 가드를 적절히 활용해 문제를 해결할 수 있었습니다.
Typescript를 활용한 안정적인 컴포넌트 설계의 중요성을 다시 한번 느끼게 되는 것 같습니다!

아직 많이 부족하기 때문에 조언은 언제나 환영입니다 :)
'공부 > 트러블 슈팅' 카테고리의 다른 글
[트러블 슈팅] 스와이퍼가 메인 -> 지도 -> 메인 이동 시 사라지는 이슈 해결 (0) | 2024.11.14 |
---|---|
[트러블 슈팅] Next.js에서 이미지 최적화로 FCP 및 LCP 성능 개선하기: 이미지 압축을 통한 로딩 속도 향상 (0) | 2024.11.14 |
[트러블 슈팅] SVG 속성 오류 해결하기 - <svg> Height 속성에서 Expected length, "auto" 오류를 수정하는 방법 (1) | 2024.11.14 |
[트러블 슈팅] Next Vercel 배포 환경 개선 - Dev 환경 최신화 문제 해결 (0) | 2024.10.25 |
[트러블 슈팅] issue daily - 로그인 상태 새로고침 시 유지하기 (1) | 2024.10.18 |
[트러블 슈팅] supabase에서 Google, Kakao 로그인 시 신규 사용자 저장 오류 (2) | 2024.10.14 |
[트러블 슈팅] Next.js generateStaticParams 정적 경로 생성의 오해와 해결 과정 (0) | 2024.10.07 |
[트러블 슈팅] 카카오맵 API를 활용한 캠핑장 데이터 렌더링 최적화 (0) | 2024.09.19 |