Skip to content

Modernize React APIs and adopt current best practices #649

@ravisuhag

Description

@ravisuhag

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

  1. Update peer dependency from ^18 || ^19 to ^19 in packages/raystack/package.json
  2. Remove deprecated ref wrapper from all 63 component files — accept ref as a regular prop instead
  3. Remove displayName assignments — named function declarations auto-infer the name
  4. Update type patterns — replace ElementRef<typeof Primitive> with direct HTML element types where possible
  5. Verify third-party compatibility — ensure Base UI, Radix, and cmdk primitives work with ref-as-prop

Optional (adopt new APIs where beneficial)

  1. Replace <Context.Provider> with <Context> if used anywhere
  2. Evaluate use() hook for async data patterns
  3. Update docs/stories referencing old patterns

Validation

  1. Run full test suite and verify no regressions
  2. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions