Skip to content

feat: React 19 migration - 5#712

Merged
rohanchkrabrty merged 8 commits intomainfrom
migrate-react-19-5
Mar 24, 2026
Merged

feat: React 19 migration - 5#712
rohanchkrabrty merged 8 commits intomainfrom
migrate-react-19-5

Conversation

@rohanchkrabrty
Copy link
Contributor

@rohanchkrabrty rohanchkrabrty commented Mar 17, 2026

Summary

  • Removed forwardRef wrappers (Cases A & B) across popover, preview-card, progress, radio, scroll-area, search, select, and sidebar components
  • Migrated Context provider syntax from <Context.Provider value={…}> to <Context value={…}> shorthand in progress, select, and sidebar
  • Replaced ComponentPropsWithoutRef with ComponentProps and updated named imports across select and sidebar

Components migrated:

  • popover
  • preview-card
  • progress
  • radio
  • scroll-area
  • search
  • select
  • separator
  • side-panel
  • sidebar

Summary by CodeRabbit

  • Refactor
    • Simplified multiple UI components' implementations; runtime behavior and visuals remain unchanged.
    • Several components now accept standard HTML element props (broader/native prop support).
    • Display names standardized for clearer tooling and debugging.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR converts many components across the raystack package from React.forwardRef wrappers to plain function components, removes ref forwarding in many places, adjusts TypeScript prop types (often switching to native ComponentProps or including/excluding ref), and replaces some .Provider context wrappers with direct context element usage.

Changes

Cohort / File(s) Summary
Popover & Preview Card
packages/raystack/components/popover/popover.tsx, packages/raystack/components/preview-card/preview-card.tsx
Converted PopoverContent and PreviewCardContent/Viewport from forwardRef to plain functions; PopoverContent/PreviewCardContent prop types updated to include ref where applicable; removed ElementRef/forwardRef imports.
Select family
packages/raystack/components/select/*
Rewrote SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup/Label/Separator, SelectRoot to plain functions; updated many prop interfaces to extend primitive props or native ComponentProps, removed ref forwarding, and replaced some Context.Provider wrappers with direct context usage.
Progress components
packages/raystack/components/progress/*
Replaced forwardRef components (ProgressRoot, ProgressTrack, ProgressLabel, ProgressValue) with plain functions; removed ref forwarding and ElementRef usage; adjusted context usage and displayName strings.
Sidebar & SidePanel
packages/raystack/components/sidebar/*, packages/raystack/components/side-panel/side-panel.tsx
Converted SidebarRoot, SidebarMain, SidebarItem, SidebarHeader, SidebarFooter, SidebarNavigationGroup and SidePanel pieces to plain functions; changed prop inheritance to ComponentProps<'...'>, removed ref forwarding, and replaced some .Provider wrappers with direct context elements; added/changed displayName(s).
ScrollArea & Scrollbar
packages/raystack/components/scroll-area/*
ScrollArea and ScrollAreaScrollbar switched from forwardRef to plain functions; ref forwarding removed; preserved structure and defaults.
Radio
packages/raystack/components/radio/radio.tsx
RadioGroup and RadioItem converted to plain functions; removed forwardRef/ref forwarding and simplified signatures.
Search
packages/raystack/components/search/search.tsx
Search changed from forwardRef to plain function; removed ref plumbing to InputField while preserving behavior.
Misc small conversions
packages/raystack/components/progress/progress-misc.tsx, other minor files
Multiple small components (labels, values, misc primitives) converted from forwardRef to named functions; displayName updates and prop-type normalizations.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

Do not merge, BREAKING CHANGE

Suggested reviewers

  • rohilsurana
  • rsbh
  • paanSinghCoder

Poem

🐰 Refs let go, the code hops light and free,

Functions now wear props where refs used to be,
Context speaks plainly, no Provider guise,
A rabbit cheers changes with bright, eager eyes,
Hooray for simpler components — hip, hop, wee!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: React 19 migration - 5' accurately summarizes the main change—a batch migration of multiple components to React 19 standards, specifically removing forwardRef wrappers and updating context provider syntax.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate-react-19-5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link

vercel bot commented Mar 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Mar 24, 2026 6:22am

@rohanchkrabrty rohanchkrabrty changed the title chore: React 19 migration — batch 5 (popover → sidebar) chore: React 19 migration — 5 Mar 17, 2026
@rohanchkrabrty rohanchkrabrty changed the title chore: React 19 migration — 5 feat: React 19 migration — 5 Mar 17, 2026
@rohanchkrabrty rohanchkrabrty changed the title feat: React 19 migration — 5 feat: React 19 migration - 5 Mar 17, 2026
@ravisuhag ravisuhag linked an issue Mar 20, 2026 that may be closed by this pull request
Base automatically changed from migrate-react-19-4 to main March 24, 2026 05:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/raystack/components/sidebar/sidebar-misc.tsx (1)

45-68: ⚠️ Potential issue | 🟡 Minor

Use section props for SidebarNavigationGroup.

Line 45 exposes div props/ref via ComponentProps<'div'>, but the component renders a <section> element. This creates a typing mismatch: consumers will receive an HTMLElement (from the section), but the type contract promises HTMLDivElement. Use ComponentProps<'section'> to match the rendered element.

-export interface SidebarNavigationGroupProps extends ComponentProps<'div'> {
+export interface SidebarNavigationGroupProps extends ComponentProps<'section'> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/sidebar/sidebar-misc.tsx` around lines 45 - 68,
SidebarNavigationGroupProps currently extends ComponentProps<'div'> while
SidebarNavigationGroup renders a <section>, causing an incorrect prop/ref type;
update the props type to extend ComponentProps<'section'> (change
ComponentProps<'div'> to ComponentProps<'section'>) so the
SidebarNavigationGroupProps and SidebarNavigationGroup signatures correctly
reflect the rendered element and its attributes.
🧹 Nitpick comments (2)
packages/raystack/components/progress/progress-misc.tsx (1)

7-17: Ref support removed from ProgressLabel and ProgressValue.

These components no longer support ref forwarding. For label and value display components, direct DOM access is typically less critical. If consumers need ref access, consider adding ref as an optional prop similar to other components in this PR.

Also applies to: 21-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/progress/progress-misc.tsx` around lines 7 - 17,
ProgressLabel (and ProgressValue) currently lack ref forwarding; reintroduce
optional ref support by converting the component to accept a ref prop and
forward it to ProgressPrimitive.Label (and ProgressPrimitive.Value) using
React.forwardRef or the same pattern used by other components in this PR; ensure
the prop typing includes a ref (matching ProgressPrimitive.Label.Props /
ProgressPrimitive.Value.Props) and pass that ref through to the underlying
primitive component while preserving className and other props (reference
symbols: ProgressLabel, ProgressValue, ProgressPrimitive.Label,
ProgressPrimitive.Value).
packages/raystack/components/radio/radio.tsx (1)

7-11: Ref support removed from RadioGroup and RadioItem.

Unlike other components in this PR that convert ref to a regular prop, these components have completely removed ref support. If consumers need to access the underlying DOM elements, this is a breaking change.

Consider adding ref as an optional prop if ref forwarding is needed:

♻️ Optional: Add ref support if needed
-function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
+function RadioGroup({ ref, className, ...props }: RadioGroupPrimitive.Props) {
   return (
-    <RadioGroupPrimitive className={cx(styles.radio, className)} {...props} />
+    <RadioGroupPrimitive ref={ref} className={cx(styles.radio, className)} {...props} />
   );
 }
-function RadioItem({ className, ...props }: RadioPrimitive.Root.Props) {
+function RadioItem({ ref, className, ...props }: RadioPrimitive.Root.Props) {
   return (
-    <RadioPrimitive.Root {...props} className={cx(styles.radioitem, className)}>
+    <RadioPrimitive.Root ref={ref} {...props} className={cx(styles.radioitem, className)}>
       <RadioPrimitive.Indicator className={styles.indicator} />
     </RadioPrimitive.Root>
   );
 }

Also applies to: 15-21

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/radio/radio.tsx` around lines 7 - 11, RadioGroup
(and RadioItem) currently drop ref support, which is a breaking change; change
the component to accept and forward refs by converting the function to use
React.forwardRef and typing it appropriately (e.g., const RadioGroup =
React.forwardRef<HTMLDivElement, RadioGroupPrimitive.Props>(( { className,
...props }, ref) => { return <RadioGroupPrimitive ref={ref}
className={cx(styles.radio, className)} {...props} />; })); do the same pattern
for RadioItem (forward the ref to the underlying primitive), ensure types use
ComponentPropsWithRef/appropriate HTML element type and export the forwarded-ref
component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/raystack/components/progress/progress-root.tsx`:
- Around line 47-59: The computed percentage (const percentage) can become
Infinity/NaN when max <= min or fall outside 0-100 if value is out of range;
clamp and validate it before using in ProgressContext and inline style: compute
a safePercentage by first handling null value, ensuring denominator > 0,
bounding raw ((value-min)/(max-min))*100 to [0,100], and fallback to 0 on
invalid math, then pass safePercentage into ProgressContext value and the inline
CSS variable '--rs-progress-percentage' used in ProgressPrimitive.Root
(referencing percentage, value, min, max, ProgressContext, and
ProgressPrimitive.Root).

In `@packages/raystack/components/search/search.tsx`:
- Around line 16-29: The Search component is destructuring a ref but not wrapped
with React.forwardRef, and InputField likewise doesn't forward refs to its
underlying input; wrap both Search and InputField with React.forwardRef so they
receive a ref parameter (instead of destructuring ref from props) and pass that
ref down: in Search accept (props, ref) and forward the ref to the child
InputField (e.g., <InputField ref={ref} ...>), and in InputField accept (props,
ref) and attach the ref to the actual <input> element (e.g., <input ref={ref}
...>), updating their prop typings accordingly.

In `@packages/raystack/components/select/select-content.tsx`:
- Around line 12-28: The SelectContent component now accepts ref as a regular
prop (SelectContent) which only works in React 19; either update the package
peerDependencies to require React 19 (set peerDependencies "react" to "^19") in
packages/raystack/package.json, or restore the previous forwardRef wrapping for
SelectContent and the other functions where forwardRef was removed (the
components referenced at the other changed blocks) so refs continue to work with
React 18 consumers—pick one approach and apply it consistently across all
components that had forwardRef removed.

In `@packages/raystack/components/select/select-value.tsx`:
- Around line 71-73: When item is an array the ref isn't forwarded to the
rendered node, causing inconsistent behavior; wrap the multiple-selection render
in a simple element that accepts the ref and forward the ref to that wrapper.
Update the branch in select-value.tsx that returns <SelectMultipleValue
data={item} /> to instead render a wrapper (e.g., <span>) with the incoming ref
attached and render <SelectMultipleValue data={item} /> inside it so the ref is
consistently available for both single and multiple selection paths; reference
the existing item array check and the SelectMultipleValue component when making
the change.

---

Outside diff comments:
In `@packages/raystack/components/sidebar/sidebar-misc.tsx`:
- Around line 45-68: SidebarNavigationGroupProps currently extends
ComponentProps<'div'> while SidebarNavigationGroup renders a <section>, causing
an incorrect prop/ref type; update the props type to extend
ComponentProps<'section'> (change ComponentProps<'div'> to
ComponentProps<'section'>) so the SidebarNavigationGroupProps and
SidebarNavigationGroup signatures correctly reflect the rendered element and its
attributes.

---

Nitpick comments:
In `@packages/raystack/components/progress/progress-misc.tsx`:
- Around line 7-17: ProgressLabel (and ProgressValue) currently lack ref
forwarding; reintroduce optional ref support by converting the component to
accept a ref prop and forward it to ProgressPrimitive.Label (and
ProgressPrimitive.Value) using React.forwardRef or the same pattern used by
other components in this PR; ensure the prop typing includes a ref (matching
ProgressPrimitive.Label.Props / ProgressPrimitive.Value.Props) and pass that ref
through to the underlying primitive component while preserving className and
other props (reference symbols: ProgressLabel, ProgressValue,
ProgressPrimitive.Label, ProgressPrimitive.Value).

In `@packages/raystack/components/radio/radio.tsx`:
- Around line 7-11: RadioGroup (and RadioItem) currently drop ref support, which
is a breaking change; change the component to accept and forward refs by
converting the function to use React.forwardRef and typing it appropriately
(e.g., const RadioGroup = React.forwardRef<HTMLDivElement,
RadioGroupPrimitive.Props>(( { className, ...props }, ref) => { return
<RadioGroupPrimitive ref={ref} className={cx(styles.radio, className)}
{...props} />; })); do the same pattern for RadioItem (forward the ref to the
underlying primitive), ensure types use ComponentPropsWithRef/appropriate HTML
element type and export the forwarded-ref component.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0b48c3f7-cafe-4b8b-b969-834dee8c1f33

📥 Commits

Reviewing files that changed from the base of the PR and between 727ec4d and 429bb11.

📒 Files selected for processing (20)
  • packages/raystack/components/popover/popover.tsx
  • packages/raystack/components/preview-card/preview-card.tsx
  • packages/raystack/components/progress/progress-misc.tsx
  • packages/raystack/components/progress/progress-root.tsx
  • packages/raystack/components/progress/progress-track.tsx
  • packages/raystack/components/radio/radio.tsx
  • packages/raystack/components/scroll-area/scroll-area-scrollbar.tsx
  • packages/raystack/components/scroll-area/scroll-area.tsx
  • packages/raystack/components/search/search.tsx
  • packages/raystack/components/select/select-content.tsx
  • packages/raystack/components/select/select-item.tsx
  • packages/raystack/components/select/select-misc.tsx
  • packages/raystack/components/select/select-root.tsx
  • packages/raystack/components/select/select-trigger.tsx
  • packages/raystack/components/select/select-value.tsx
  • packages/raystack/components/side-panel/side-panel.tsx
  • packages/raystack/components/sidebar/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-main.tsx
  • packages/raystack/components/sidebar/sidebar-misc.tsx
  • packages/raystack/components/sidebar/sidebar-root.tsx

Comment on lines +47 to +59
const percentage = value === null ? 0 : ((value - min) * 100) / (max - min);

return (
<ProgressContext.Provider
value={{ variant: variant ?? 'linear', value, percentage }}
return (
<ProgressContext
value={{ variant: variant ?? 'linear', value, percentage }}
>
<ProgressPrimitive.Root
className={progress({ variant, className })}
style={
{
...style,
'--rs-progress-percentage': percentage
} as CSSProperties
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard the derived percentage against invalid ranges.

Line 47 can produce Infinity/NaN when max <= min, and out-of-range values can push --rs-progress-percentage past the expected 0-100 band. That leaks into both the context value and the inline style.

🩹 Proposed fix
-  const percentage = value === null ? 0 : ((value - min) * 100) / (max - min);
+  const range = max - min;
+  const percentage =
+    value == null || range <= 0
+      ? 0
+      : Math.min(100, Math.max(0, ((value - min) * 100) / range));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/progress/progress-root.tsx` around lines 47 -
59, The computed percentage (const percentage) can become Infinity/NaN when max
<= min or fall outside 0-100 if value is out of range; clamp and validate it
before using in ProgressContext and inline style: compute a safePercentage by
first handling null value, ensuring denominator > 0, bounding raw
((value-min)/(max-min))*100 to [0,100], and fallback to 0 on invalid math, then
pass safePercentage into ProgressContext value and the inline CSS variable
'--rs-progress-percentage' used in ProgressPrimitive.Root (referencing
percentage, value, min, max, ProgressContext, and ProgressPrimitive.Root).

Comment on lines +16 to +29
export function Search({
ref,
className,
disabled,
placeholder = 'Search',
size,
showClearButton,
onClear,
value,
onChange,
width = '100%',
variant = 'default',
...props
}: SearchProps) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify InputField's ref handling
cat packages/raystack/components/input-field/input-field.tsx | head -100

Repository: raystack/apsara

Length of output: 2633


🏁 Script executed:

# Read search.tsx to see the full context around lines 16-29 and 52-65
wc -l packages/raystack/components/search/search.tsx

Repository: raystack/apsara

Length of output: 108


🏁 Script executed:

# Read the search.tsx file with context around the mentioned lines
sed -n '1,80p' packages/raystack/components/search/search.tsx

Repository: raystack/apsara

Length of output: 1769


🏁 Script executed:

# Check the complete InputField to see if it's wrapped in forwardRef
cat packages/raystack/components/input-field/input-field.tsx

Repository: raystack/apsara

Length of output: 4556


Search component does not properly forward ref to InputField due to missing forwardRef wrapper.

The Search component destructures ref but is not wrapped in forwardRef, so React filters out the ref before it reaches the function. Additionally, InputField is a plain function component and does not use forwardRef, so even if the ref were passed, it would be silently ignored at runtime.

To fix this, both components need to be wrapped with React.forwardRef(), and InputField should forward the ref to the underlying <input> element.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/search/search.tsx` around lines 16 - 29, The
Search component is destructuring a ref but not wrapped with React.forwardRef,
and InputField likewise doesn't forward refs to its underlying input; wrap both
Search and InputField with React.forwardRef so they receive a ref parameter
(instead of destructuring ref from props) and pass that ref down: in Search
accept (props, ref) and forward the ref to the child InputField (e.g.,
<InputField ref={ref} ...>), and in InputField accept (props, ref) and attach
the ref to the actual <input> element (e.g., <input ref={ref} ...>), updating
their prop typings accordingly.

Comment on lines +71 to +73
if (Array.isArray(item)) {
return <SelectMultipleValue data={item} />;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Ref is not forwarded when rendering SelectMultipleValue.

When item is an array (multiple selection with more than one item), the ref is not passed to SelectMultipleValue. This creates inconsistent behavior where the ref works for single selections but is lost for multiple selections.

Consider wrapping in a span to maintain ref consistency:

🔧 Proposed fix
   if (Array.isArray(item)) {
-    return <SelectMultipleValue data={item} />;
+    return (
+      <span ref={ref} className={className} {...props}>
+        <SelectMultipleValue data={item} />
+      </span>
+    );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Array.isArray(item)) {
return <SelectMultipleValue data={item} />;
}
if (Array.isArray(item)) {
return (
<span ref={ref} className={className} {...props}>
<SelectMultipleValue data={item} />
</span>
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/select/select-value.tsx` around lines 71 - 73,
When item is an array the ref isn't forwarded to the rendered node, causing
inconsistent behavior; wrap the multiple-selection render in a simple element
that accepts the ref and forward the ref to that wrapper. Update the branch in
select-value.tsx that returns <SelectMultipleValue data={item} /> to instead
render a wrapper (e.g., <span>) with the incoming ref attached and render
<SelectMultipleValue data={item} /> inside it so the ref is consistently
available for both single and multiple selection paths; reference the existing
item array check and the SelectMultipleValue component when making the change.

@rohanchkrabrty rohanchkrabrty merged commit 8600988 into main Mar 24, 2026
4 of 5 checks passed
@rohanchkrabrty rohanchkrabrty deleted the migrate-react-19-5 branch March 24, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modernize React APIs and adopt current best practices

2 participants