[bug]: (sidebar-13): Function components cannot be given refs when using Sidebar in a Dialog
Describe the bug
When using the Sidebar component within a Dialog component from shadcn/ui, a warning is thrown in the console:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Affected component/components
SidebarMenuButton
How to reproduce
- Set up a new project with
shadcn/uicomponents installed. - Create a
settings-dialog.tsxfile with the provided code that nests a Sidebar inside a Dialog. - Create a
page.tsxfile in theapp/dashboarddirectory with the provided code to render the SettingsDialog. - Run the application using
npm run devor equivalent. - Open the browser console and trigger the SettingsDialog by clicking the "Open Dialog" button.
- Observe the warning:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Codesandbox/StackBlitz link
https://ui.shadcn.com/blocks/sidebar#sidebar-13
Logs
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of `SlotClone`.
SidebarMenuButton@http://localhost:5173/src/components/ui/sidebar.tsx:497:27
SlotClone<@http://localhost:5173/node_modules/.vite/deps/chunk-HDHGXYFG.js?v=88a60195:73:38
Slot<@http://localhost:5173/node_modules/.vite/deps/chunk-HDHGXYFG.js?v=88a60195:54:38
Primitive</Node<@http://localhost:5173/node_modules/.vite/deps/chunk-L4XL26MT.js?v=88a60195:41:44
DialogTrigger<@http://localhost:5173/node_modules/.vite/deps/@radix-ui_react-dialog.js?v=88a60195:1156:48
DialogTrigger@http://localhost:5173/src/components/ui/dialog.tsx:25:23
li
SidebarMenuItem@http://localhost:5173/src/components/ui/sidebar.tsx:467:25
ul
SidebarMenu@http://localhost:5173/src/components/ui/sidebar.tsx:454:21
div
SidebarFooter@http://localhost:5173/src/components/ui/sidebar.tsx:360:23
div
div
div
Sidebar@http://localhost:5173/src/components/ui/sidebar.tsx:138:17
Provider@http://localhost:5173/node_modules/.vite/deps/chunk-BVAFDEYC.js?v=88a60195:51:47
Dialog@http://localhost:5173/node_modules/.vite/deps/@radix-ui_react-dialog.js?v=88a60195:1127:7
Dialog@http://localhost:5173/src/components/ui/dialog.tsx:14:16
AppSidebar@http://localhost:5173/src/components/app-sidebar.tsx?t=1741244310649:19:27
div
Provider@http://localhost:5173/node_modules/.vite/deps/chunk-BVAFDEYC.js?v=88a60195:51:47
TooltipProvider@http://localhost:5173/node_modules/.vite/deps/@radix-ui_react-tooltip.js?v=88a60195:2278:7
TooltipProvider@http://localhost:5173/src/components/ui/tooltip.tsx:13:25
SidebarProvider@http://localhost:5173/src/components/ui/sidebar.tsx:40:25
App@http://localhost:5173/src/App.tsx?t=1741244310649:18:57
System Info
System: Windows 11 pro
Binaries: Node.js v22.14.0, npm 11.1.0
Browsers: Chrome 134.0.6998.36
`package.json`
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@tailwindcss/vite": "^4.0.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.477.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"swr": "^2.3.2",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/node": "^22.13.9",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react-swc": "^3.8.0",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
Before submitting
- [x] I've made research efforts and searched the documentation
- [x] I've searched for existing issues
Can I work on this issue @suprunchuk
The Drawer component also has this problem.
Same happens for Input components inside Form component
@shadcn Hi, can you fix this misunderstanding as soon as possible??
@suprunchuk Can you share the code or a codesandbox link? thank you
@suprunchuk Can you share the code or a codesandbox link? thank you
I just took the sidebar-13 component and added it to the clean project with Vite
When you use asChild with a Radix UI component like DropdownMenuTrigger, it:
- Doesn't render its own DOM element
- Clones the child element you provide (in this case,
SidebarMenuButton) - Spreads all necessary props to that child element
The problem occurs when the child component (SidebarMenuButton) doesn't properly handle these forwarded props or doesn't forward them to its own HTML elements.
Fix the SidebarMenuButton component: Make sure it properly forwards refs and event handlers.
function SidebarMenuButton(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
}: React.ComponentProps<"button"> & {
asChild?: boolean;
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>,
ref: React.ForwardedRef<HTMLButtonElement>
) {
const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar();
const button = (
<Comp
ref={ref} // Forward the ref
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
);
if (!tooltip) {
return button;
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
};
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
);
}
// Forward ref using React.forwardRef after function definition
const ForwardedSidebarMenuButton = React.forwardRef(SidebarMenuButton);
ForwardedSidebarMenuButton.displayName = "SidebarMenuButton";
//Export
export { ForwardedSidebarMenuButton as SidebarMenuButton } ;
Solved by upgrading to React 19. In React 19, you can pass ref as a prop.
Solved by upgrading to React 19. In React 19, you can pass ref as a prop.
works for me
Same happens for Input components inside Form component
True! , based @thanhttb solution, and other info I gathered on React.forward, I created a copy of the Shadcn Input component, wrapped it in a React.forward function, and then used this new component in my form.
constant MyFormComponent = () => {
..........
**const InputWithRefF = React.forwardRef((props, ref) => (
<Input placeholder="shadcn" />
));**
return (
<Form {... form}>
........
<FormControl>
**<InputWithRefF {...field} />**
...........
)
};
export default MyFormComponent;
p.s i'm just starting with React, so I don't know if this code is 100% correct. it does solve the problem though. My setup: Vite React v18 + TS
Solved by upgrading to React 19. In React 19, you can pass ref as a prop.
Thanks a lot 😊
There a some things even LLMs can't fix, this simple fix is one of them 😀
I had the same problem when using <Input /> Inside <Form />
This fixed it
function Input(
{ className, type, ...props }: React.ComponentProps<'input'>,
ref: React.ForwardedRef<HTMLInputElement>,
) {
return (
<input
ref={ref}
// rest of shadcn props
{...props}
/>
);
}
// Forward ref using React.forwardRef after function definition
const ForwardedInput = React.forwardRef(Input);
ForwardedInput.displayName = 'Input';
export { ForwardedInput as Input };
Hey @shadcn, I wanted to add more context to this issue as I'm experiencing the same problem. I've created a minimal reproduction on StackBlitz to help demonstrate it.
Environment:
- React: 18.3.1
- Framework: Vite
shadcn/uicomponents:button,popover,label,inputadded via the latest CLI.
Problem:
When following the exact code example from the Popover documentation, the component fails to work and throws a console warning about refs. This happens because the default Button component generated by the CLI does not use React.forwardRef, which is required by Radix's Slot primitive when using the asChild prop in React 18.
Console Warning:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of `Primitive.button.SlotClone`.
Reproduction Link: Here is a StackBlitz that clearly reproduces the issue with a fresh install: shadcn-popover-react18-ref-issue
Confirmation of the Fix:
I can confirm that manually editing src/components/ui/button.tsx to wrap the Button component in React.forwardRef completely resolves the issue.
It seems the CLI template for the Button component may need to be updated to include React.forwardRef by default to ensure out-of-the-box compatibility for React 18 users.
Hope this helps get the issue resolved
Skeleton has this bug too, uses latest command line
npx shadcn@latest add skeleton -o
Fixed as
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index d789105..1b82ace 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -1,15 +1,19 @@
+import * as React from "react"
+
import { cn } from "@/lib/utils"
-function Skeleton({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) {
+const Skeleton = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
return (
<div
- className={cn("animate-pulse rounded-md bg-gray-300 dark:bg-gray-600", className)}
+ ref={ref}
+ className={cn("animate-pulse rounded-md bg-slate-900/10 dark:bg-slate-50/10", className)}
{...props}
/>
)
-}
+})
+Skeleton.displayName = 'Skeleton'
export { Skeleton }
Subscribed. This is a pretty annoying bug in a lot of shadcn/ui components, like the Button component. As said before, upgrading to react 19 fixed it for me.
Subscribed. This is a pretty annoying bug in a lot of shadcn/ui components, like the Button component. As said before, upgrading to react 19 fixed it for me.
This have worked for me too (becuase of https://react.dev/blog/2024/12/05/react-19#ref-as-a-prop)