Summary
Apsara's dev dependency is already on the latest React, but the codebase still uses legacy patterns. We should fully adopt modern React APIs and drop legacy support.
Current state
- Dev dependency:
react@^19.0.1
- Peer dependency:
react@^18 || ^19 (needs updating to ^19)
- Deprecated
forwardRef usage: 164 occurrences across 63 files — ref is now a regular prop and the wrapper is unnecessary
displayName assignments: 60+ components (only needed because of the deprecated wrapper)
useImperativeHandle: not used anywhere
- Other deprecated APIs: none found — no class components, no string refs, no legacy context, no
react-dom/test-utils
Key changes in modern React
ref is a regular prop — the deprecated forwardRef wrapper is no longer needed
displayName auto-inferred — named function declarations don't need explicit displayName
ref cleanup functions — ref callbacks can return a cleanup function
use() hook — read resources (promises, context) during render
useActionState — manages form action state
useOptimistic — optimistic UI updates
<Context> as provider — no need for <Context.Provider>
- Document metadata — native
<title>, <meta>, <link> hoisting
- Stylesheet precedence — built-in
<link rel="stylesheet" precedence="...">
- Async script support —
<script async> deduplication
Migration steps
Required
- Update peer dependency from
^18 || ^19 to ^19 in packages/raystack/package.json
- Remove deprecated ref wrapper from all 63 component files — accept
ref as a regular prop instead
- Remove
displayName assignments — named function declarations auto-infer the name
- Update type patterns — replace
ElementRef<typeof Primitive> with direct HTML element types where possible
- Verify third-party compatibility — ensure Base UI, Radix, and cmdk primitives work with ref-as-prop
Optional (adopt new APIs where beneficial)
- Replace
<Context.Provider> with <Context> if used anywhere
- Evaluate
use() hook for async data patterns
- Update docs/stories referencing old patterns
Validation
- Run full test suite and verify no regressions
- Test with consuming apps to ensure nothing breaks
Migration example
// Before (legacy pattern)
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => (
<button ref={ref} {...props} />
));
Button.displayName = 'Button';
// After (modern pattern)
function Button({ ref, ...props }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {
return <button ref={ref} {...props} />;
}
Affected components (63 files)
Accordion, Amount, Avatar, Breadcrumb, Button, Callout, Checkbox, CodeBlock, Combobox, Command, CopyButton, DataTable, Dialog, DropdownMenu, Flex, Grid, Headline, IconButton, InputField, Link, Navbar, Popover, Radio, ScrollArea, Search, Select, Sheet, Sidebar, Slider, Spinner, Switch, Table, Tabs, Text, TextArea
Breaking change
This is a breaking change — consumers on older React versions will need to upgrade.
Files
packages/raystack/package.json (peer dep)
packages/raystack/components/ (all 63 component files)
Summary
Apsara's dev dependency is already on the latest React, but the codebase still uses legacy patterns. We should fully adopt modern React APIs and drop legacy support.
Current state
react@^19.0.1react@^18 || ^19(needs updating to^19)forwardRefusage: 164 occurrences across 63 files —refis now a regular prop and the wrapper is unnecessarydisplayNameassignments: 60+ components (only needed because of the deprecated wrapper)useImperativeHandle: not used anywherereact-dom/test-utilsKey changes in modern React
refis a regular prop — the deprecatedforwardRefwrapper is no longer neededdisplayNameauto-inferred — named function declarations don't need explicitdisplayNamerefcleanup functions — ref callbacks can return a cleanup functionuse()hook — read resources (promises, context) during renderuseActionState— manages form action stateuseOptimistic— optimistic UI updates<Context>as provider — no need for<Context.Provider><title>,<meta>,<link>hoisting<link rel="stylesheet" precedence="..."><script async>deduplicationMigration steps
Required
^18 || ^19to^19inpackages/raystack/package.jsonrefas a regular prop insteaddisplayNameassignments — named function declarations auto-infer the nameElementRef<typeof Primitive>with direct HTML element types where possibleOptional (adopt new APIs where beneficial)
<Context.Provider>with<Context>if used anywhereuse()hook for async data patternsValidation
Migration example
Affected components (63 files)
Accordion, Amount, Avatar, Breadcrumb, Button, Callout, Checkbox, CodeBlock, Combobox, Command, CopyButton, DataTable, Dialog, DropdownMenu, Flex, Grid, Headline, IconButton, InputField, Link, Navbar, Popover, Radio, ScrollArea, Search, Select, Sheet, Sidebar, Slider, Spinner, Switch, Table, Tabs, Text, TextArea
Breaking change
This is a breaking change — consumers on older React versions will need to upgrade.
Files
packages/raystack/package.json(peer dep)packages/raystack/components/(all 63 component files)