Repository navigation
fix: unwrap RSC lazy elements passed as icon props - #901
mattrothenberg wants to merge 3 commits into
Conversation
|
I'm Bonk, and I've done a quick review of your PR. 1 finding posted inline. Review notes
|
|
✅ Approved — No issues found. The The decision is advisory: this comment never approves, blocks or requests changes on the pull request. ✅ All checks passed🔒 Security: ✅ LGTMNo security concerns. 📖 AGENTS.md: ✅ LGTMThis pull request follows the repository conventions: the 📋 Code Quality: ✅ LGTMI traced
The React 18 concern raised earlier in the discussion is handled: the helper no longer throws for One harmless detail: ⚡ Performance: ✅ LGTMFor values that are not lazy references, the helper does one 📝 Documentation: ✅ LGTMThe changeset and the Reviewed commit: |
commit: |
🚀 Deploying Preview to Cloudflare 🚀Preview URL: https://fix-rsc-lazy-icon-kumo-docs.design-engineering.workers.dev (commit 10a1773)This URL reflects your latest Preview deploymentPreview Deployments by commit
|
React 18's Children.toArray does not resolve react.lazy values, so it threw for Flight lazy elements and for React.lazy icon components. Resolve the lazy reference directly instead, and document element icons in USAGE.md.
|
Addressed review feedback in a12d66e:
|
📐 Bundle SizeConsumer fixtures bundled against
npm tarball: 566 files, 1.56 MB packed, 7.30 MB unpacked.
|
Drop the _init/_payload access in favour of the public Children.toArray, as Base UI and Ark UI do. Fall back to the original value when toArray throws for anything other than a pending thenable: React 18, which cannot carry Flight lazy elements, and React.lazy components resolving to forwardRef or memo objects such as Phosphor icons. Skip the Flight element tests on React 18.

Fixes #891.
Problem
When an element is passed as
iconfrom a React Server Component (e.g.<Button icon={<Star />} />in a Next.js App Router page), React Flight can deliver it to the client as a lazy reference ($$typeof: Symbol.for("react.lazy")) instead of an element. This is react#32392.Kumo's icon helpers did:
The lazy reference fails
isValidElement, so it was rendered as a component. React resolves it, gets an element instead of a component, and throws error #306.Fix
Adds an internal
unwrapLazyElementhelper (not exported from the package) that unwraps a lazy reference to its element viaReact.Children.toArray, and calls it at the top of every icon helper. The rest of each helper is unchanged, soDropdownMenustill merges its sizing classes into element icons.This mirrors Base UI's
unwrapLazyRenderPropin@base-ui/react/internals/useRenderElementand Ark UI'sasChildfix, which work around the same React issue (closed as not planned). It uses only public React APIs apart from theSymbol.for("react.lazy")check. Values that are not lazy references, or that don't unwrap to an element, are returned unchanged.Unlike Base UI's
renderprop,iconalso accepts components, sotoArraycan throw: on React 18, which does not resolve lazy values (and cannot receive Flight elements, since Server Components require React 19), and for aReact.lazycomponent that resolves to aforwardReformemoobject such as a Phosphor icon. In those cases the helper rethrows a pending thenable so React suspends, and otherwise returns the original value, which renders as a component as before. A genuine error resurfaces when React renders that value.Applied to:
Button/LinkButtonBadgeDropdownMenuitems (Item,LinkItem,SubTrigger, etc.)Sidebar.MenuButtonInputGroup.ButtonTests
Each component gets a test that passes a Flight-style lazy reference as
icon; all 5 fail with React error #306 without the fix. Also adds unit tests forunwrapLazyElement.