primitives
primitives copied to clipboard
Components used with "asChild" should only accept a single child
Feature request
Overview
Current state of affairs, using a Dialog as an example:
<Dialog.Root>
<Dialog.Trigger />
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content asChild>
{/* runtime error here */}
<Dialog.Title />
<Dialog.Description />
<Dialog.Close />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
When setting asChild on a component, an error is thrown at runtime because there must be a single child.
The type system should be stricter on this and raise before the runtime error is experienced.
Who does this impact? Who is this for?
This impacts all users. The current error feedback looks something like this (excuse the next-related error stack, I haven't made a minimal reproduction):
The above error occurred in the <SlotClone> component:
at eval (webpack-internal:///./node_modules/@radix-ui/react-slot/dist/index.module.js:47:23)
at eval (webpack-internal:///./node_modules/@radix-ui/react-slot/dist/index.module.js:21:23)
at eval (webpack-internal:///./node_modules/@radix-ui/react-primitive/dist/index.module.js:44:26)
at eval (webpack-internal:///./node_modules/@radix-ui/react-dismissable-layer/dist/index.module.js:45:42)
at eval (webpack-internal:///./node_modules/@radix-ui/react-slot/dist/index.module.js:47:23)
at eval (webpack-internal:///./node_modules/@radix-ui/react-slot/dist/index.module.js:21:23)
at eval (webpack-internal:///./node_modules/@radix-ui/react-primitive/dist/index.module.js:44:26)
at eval (webpack-internal:///./node_modules/@radix-ui/react-focus-scope/dist/index.module.js:33:19)
at eval (webpack-internal:///./node_modules/@radix-ui/react-dialog/dist/index.module.js:269:28)
at eval (webpack-internal:///./node_modules/@radix-ui/react-dialog/dist/index.module.js:207:102)
at $921a889cee6df7e8$export$99c2b779aa4e8b8b (webpack-internal:///./node_modules/@radix-ui/react-presence/dist/index.module.js:30:22)
at eval (webpack-internal:///./node_modules/@radix-ui/react-dialog/dist/index.module.js:192:108)
at DialogContent (webpack-internal:///./components/Dialog.tsx:54:27)
at eval (webpack-internal:///./node_modules/@radix-ui/react-slot/dist/index.module.js:47:23)
at eval (webpack-internal:///./node_modules/@radix-ui/react-slot/dist/index.module.js:21:23)
at eval (webpack-internal:///./node_modules/@radix-ui/react-primitive/dist/index.module.js:44:26)
at eval (webpack-internal:///./node_modules/@radix-ui/react-portal/dist/index.module.js:26:24)
at $921a889cee6df7e8$export$99c2b779aa4e8b8b (webpack-internal:///./node_modules/@radix-ui/react-presence/dist/index.module.js:30:22)
at Provider (webpack-internal:///./node_modules/@radix-ui/react-context/dist/index.module.js:48:28)
at $5d3850c4d0b4e6c7$export$dad7c95542bacce0 (webpack-internal:///./node_modules/@radix-ui/react-dialog/dist/index.module.js:134:28)
at Provider (webpack-internal:///./node_modules/@radix-ui/react-context/dist/index.module.js:48:28)
at $5d3850c4d0b4e6c7$export$3ddf2d174ce01153 (webpack-internal:///./node_modules/@radix-ui/react-dialog/dist/index.module.js:78:28)
at CreateEventPopup
at article
at div
at BookingCalendar (webpack-internal:///./components/BookingCalendar.tsx:164:24)
at LoadableImpl (webpack-internal:///./node_modules/next/dist/shared/lib/loadable.js:65:9)
at main
at div
at Bookings (webpack-internal:///./pages/bookings/index.tsx:32:78)
at QueryParamProviderInner (webpack-internal:///./node_modules/use-query-params/dist/QueryParamProvider.js:28:3)
at NextAdapter (webpack-internal:///./node_modules/next-query-params/dist/next-query-params.esm.js:15:23)
at QueryParamProvider (webpack-internal:///./node_modules/use-query-params/dist/QueryParamProvider.js:47:3)
at ErrorBoundary (webpack-internal:///./components/ErrorBoundary.tsx:22:90)
at SessionProvider (webpack-internal:///./node_modules/next-auth/react/index.js:454:24)
at Hydrate (webpack-internal:///./node_modules/@tanstack/react-query/build/lib/Hydrate.mjs:30:3)
at QueryClientProvider (webpack-internal:///./node_modules/@tanstack/react-query/build/lib/QueryClientProvider.mjs:47:3)
at App (webpack-internal:///./pages/_app.tsx:34:28)
at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js:8:20742)
at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js:8:23635)
at Container (webpack-internal:///./node_modules/next/dist/client/index.js:70:9)
at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:216:26)
at Root (webpack-internal:///./node_modules/next/dist/client/index.js:407:27)
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.
window.console.error @ next-dev.js?3515:20
next-dev.js?3515:20 Error: React.Children.only expected to receive a single React element child.
at Object.onlyChild [as only] (react.development.js?1b7e:1229:1)
at eval (index.module.js?ad9d:42:50)
at renderWithHooks (react-dom.development.js?ac89:16305:1)
at updateForwardRef (react-dom.development.js?ac89:19226:1)
at beginWork (react-dom.development.js?ac89:21636:1)
at beginWork$1 (react-dom.development.js?ac89:27426:1)
at performUnitOfWork (react-dom.development.js?ac89:26557:1)
at workLoopSync (react-dom.development.js?ac89:26466:1)
at renderRootSync (react-dom.development.js?ac89:26434:1)
at recoverFromConcurrentError (react-dom.development.js?ac89:25850:1)
at performSyncWorkOnRoot (react-dom.development.js?ac89:26096:1)
at flushSyncCallbacks (react-dom.development.js?ac89:12042:1)
at eval (react-dom.development.js?ac89:25651:1) Object
window.console.error @ next-dev.js?3515:20
I think it's easy to agree that this error information isn't very clear, especially for newcomers. I personally experienced this during a refactoring exercise and it wasn't obvious what the problem was from the actions I had taken.
As a first pass, better error messages might bridge the gap more quickly than the types re-work.