Menu

#5 Update Shadcn components to match newest ref convention

open
nobody
4 days ago
4 days ago
Anonymous
No

Originally created by: matt765

Context

React 19 made ref a regular prop, which means React.forwardRef is technically no longer necessary. The shadcn/ui project has an open proposal to remove it ([shadcn-ui/ui#7268](https://github.com/shadcn-ui/ui/issues/7268)) with a corresponding PR ([shadcn-ui/ui#7278](https://github.com/shadcn-ui/ui/issues/7278)), but neither has been merged yet.

All 23 Shadcn components in src/components/common/shadcn/ currently use the forwardRef pattern. Since we're on React 19, we can start migrating — but it's worth being aware that this is still an evolving area in the ecosystem.

Important caveat

There's a known issue ([shadcn-ui/ui#7926](https://github.com/shadcn-ui/ui/issues/7926)) where removing forwardRef can break Radix UI's asChild pattern — specifically, components like PopoverTrigger wrapping a Button via asChild may fail to mount or position correctly without forwardRef. So each component needs to be tested carefully, especially ones that are commonly used with asChild composition.

Why do this?

  • Simpler component signatures — no more wrapper functions and generic type gymnastics
  • No more .displayName assignments just to make dev tools happy
  • Aligns with where React is heading
  • Also a good opportunity to replace deprecated React.ElementRef with React.ComponentRef ([shadcn-ui/ui#7920](https://github.com/shadcn-ui/ui/issues/7920))

Before / After

Before:

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, ...props }, ref) => {
    return <button ref={ref} className={className} {...props} />;
  },
);
Button.displayName = "Button";

After:

const Button = ({ className, variant, ref, ...props }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) => {
  return <button ref={ref} className={className} {...props} />;
};

The idea is straightforward — pull ref out of the second argument and into the props object, then drop the forwardRef wrapper and displayName.

Files to update

All of these live in src/components/common/shadcn/:

alert.tsx, avatar.tsx, breadcrumb.tsx, button.tsx, checkbox.tsx, command.tsx, dialog.tsx, dropdown-menu.tsx, form.tsx, input.tsx, input-group.tsx, label.tsx, popover.tsx, progress.tsx, radio-group.tsx, select.tsx, separator.tsx, slider.tsx, switch.tsx, tabs.tsx, textarea.tsx, tooltip.tsx

Also worth checking: SearchInput.tsx in src/components/layout/navbar/parts/

Done when

  • [ ] No React.forwardRef calls remain in the codebase
  • [ ] No orphaned .displayName assignments
  • [ ] React.ElementRef replaced with React.ComponentRef where applicable
  • [ ] ref is accepted as a regular prop in all migrated components
  • [ ] Components using asChild pattern (Button, Dialog, Popover, etc.) are tested and work correctly
  • [ ] Everything renders and behaves the same as before
  • [ ] TypeScript compiles without errors

Discussion


Log in to post a comment.

MongoDB Logo MongoDB