Radix Umbrella Slot Root Crash
이 문제 해결 노트에서는 9a51be0의 종속성 마이그레이션, 76d14ce의 부분적인 유형 전용 후속 조치, 7a20d6d의 런타임 수정 사항을 다룹니다.
증상
Element type is invalid와 같은 React 런타임 오류로 인해 앱이 충돌합니다.- 오류는 shadcn 스타일
asChild경로, 특히 사이드바 메뉴 구성 요소에서만 나타납니다. - 코드가 구성 요소 변수 주위에 캐스트만 추가하는 경우 TypeScript가 계속 통과할 수 있습니다.
진단
현재 소유된 포크 기본 요소를 비교합니다.
lumie-frontend/components/ui/button.tsx는const Comp = asChild ? Slot.Root : "button"를 사용합니다.lumie-frontend/components/ui/sidebar.tsx는 이제SidebarGroupLabel,SidebarGroupAction,SidebarMenuButton,SidebarMenuAction및SidebarMenuSubButton에 대해 동일한 작업을 수행합니다.
이것이 핵심 단서입니다. radix-ui 통합 패키지를 사용하면 Slot는 네임스페이스 객체이며 이전 @radix-ui/react-slot 호출 지점에서 기대했던 직접 렌더링 가능한 컴포넌트 값이 아닙니다.
근본 원인
9a51be0는 shadcn 기본 요소를 @radix-ui/react-slot와 같은 패키지별 가져오기에서 엄브렐러 가져오기 import { Slot } from "radix-ui"로 마이그레이션했습니다.
일부 호출 지점는 여전히 asChild ? Slot : ... 표현식에서 Slot를 그대로 렌더링합니다. 컴파일되었지만 런타임에 React가 구성 요소 대신 개체를 수신하고 잘못된 요소 유형 오류가 발생했습니다.
76d14ce는 sidebar.tsx에 React.ElementType 캐스트를 추가하여 입력 문제는 해결했지만 런타임 동작은 해결하지 못했습니다. 실제 수정 사항은 7a20d6d에 도착하여 렌더 타겟을 Slot.Root로 변경했습니다.
수정
radix-ui에서 가져올 때 asChild 분기에서 기본 Slot 대신 Slot.Root를 렌더링합니다.
참조 패턴으로 lumie-frontend/components/ui/button.tsx를 사용합니다. 이제 사이드바 구성 요소가 일치합니다.
예방 및 검증
- Radix 종속성 마이그레이션 후
asChild ? Slot :또는components/ui내부의 기타 베어Slot렌더링 표현식에 대해 grep을 수행합니다. React.ElementType캐스트를 유형 어댑터로만 처리합니다. 유효하지 않은 런타임 구성 요소를 유효하게 만들지는 않습니다.- 현재 소스 확인:
lumie-frontend/components/ui/button.tsx는Slot.Root를 사용합니다.lumie-frontend/components/ui/sidebar.tsx는 이전에 충돌이 발생한 모든asChild분기에 대해Slot.Root를 사용합니다.
운영 세부사항
이 문제는 dependency shape 실패입니다. TypeScript surface는 괜찮아 보여도 React는 렌더링할 수 없는 값을 받을 수 있습니다.
asChild가 관련되면 실패 component가 import 시점에 바로 죽지 않는 경우가 많습니다. 실제 consumer가 asChild branch를 렌더링할 때만 죽기 때문에 sidebar, dropdown, button composition 경로가 가장 위험합니다.
| 신호 | 의미 |
|---|---|
Element type is invalid | React가 component 대신 object 또는 undefined를 받았습니다. |
asChild에서만 crash | 일반 element branch는 괜찮고 slot branch가 잘못되었습니다. |
React.ElementType cast 후 compile 통과 | type system은 우회했지만 runtime shape는 고쳐지지 않았습니다. |
복구 절차
- owned UI primitive에서 bare
Slotrender target을 검색합니다. - import가
radix-ui에서 온 경우Slot을Slot.Root로 바꿉니다. - mismatch만 숨기던 cast는 다른 generic type 문제가 남아 있지 않다면 제거합니다.
- affected component를 default mode뿐 아니라
asChildmode로 렌더링합니다. - sidebar link, menu button, dropdown trigger, command item을 compose하는 route를 확인합니다.
검증 증거
두 수준에서 증거를 모읍니다.
- static:
rg -n "asChild \\? Slot(\\s|:)" components src app가 bare umbrellaSlotrender branch를 반환하지 않습니다. - runtime: 영향을 받은
asChildcomponent를 사용하는 page가 invalid element-type 오류 없이 렌더링됩니다. - regression: 일반 non-
asChildbranch가 기대한 native element로 계속 렌더링됩니다.
앞으로 안전한 변경
향후 Radix upgrade에서는 broad codemod를 돌리기 전에 import semantic을 먼저 확인합니다. package-specific import와 umbrella import는 같은 개념의 primitive를 제공하더라도 runtime shape가 다를 수 있습니다. migration review에는 기본 button rendering보다 slot primitive를 더 강하게 사용하는 shadcn component composition 경로를 최소 하나 포함해야 합니다.