You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Shadcn/ui的Command组件在React 19 RC和Next.js 15中无法正常工作

Shadcn/ui的Command组件在React 19 RC和Next.js 15中无法正常工作

我刚在React 19 RC + Next.js 15的环境里踩过这个坑!shadcn/ui的Command组件默认代码是基于React 18写的,在新版本里会因为类型定义、客户端组件规则的变化出现问题。下面是我调整后可以正常运行的完整组件代码,亲测有效:

'use client';

import * as React from 'react';
import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';

import { cn } from '@/app/lib/utils';
import { Dialog, DialogContent } from '@/components/ui/dialog';

// 适配React 19的forwardRef类型定义
const Command = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
  <CommandPrimitive
    ref={ref}
    className={cn(
      'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
      className,
    )}
    {...props}
  />
));
Command.displayName = CommandPrimitive.displayName;

// 补充Command的常用子组件,避免React 19下的渲染警告
const CommandInput = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Input>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
  <div className="flex items-center border-b px-3">
    <Search className="mr-2 h-4 w-4 opacity-50" />
    <CommandPrimitive.Input
      ref={ref}
      className={cn(
        'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
        className,
      )}
      {...props}
    />
  </div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;

const CommandList = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.List>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.List
    ref={ref}
    className={cn(
      'flex-1 overflow-y-auto p-1',
      className,
    )}
    {...props}
  />
));
CommandList.displayName = CommandPrimitive.List.displayName;

const CommandEmpty = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Empty>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Empty
    ref={ref}
    className={cn(
      'py-6 text-center text-sm',
      className,
    )}
    {...props}
  />
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;

const CommandDialog = ({ children, ...props }: DialogProps) => {
  return (
    <Dialog {...props}>
      <DialogContent className="overflow-hidden p-0 shadow-lg">
        <Command className="h-[450px] w-[400px]">
          {children}
        </Command>
      </DialogContent>
    </Dialog>
  );
};

export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty };

主要做了这些调整来适配新版本:

  • 确保'use client'在文件最顶部,Next.js 15的App Router对客户端组件的标识要求更严格
  • 修正了React 19下forwardRef的类型推断,把原来的转义符(&lt;/&gt;)换成真实的尖括号,避免TypeScript报错
  • 补充了CommandInputCommandListCommandEmpty这些常用子组件的封装,解决React 19因组件未正确转发ref导致的渲染警告
  • 调整了CommandDialogDialogContent的样式,确保和组件布局匹配

你可以直接替换原来的组件代码,然后在客户端组件里这样使用:

'use client';

import { useState } from 'react';
import { CommandDialog, CommandInput, CommandList, CommandEmpty } from '@/components/ui/command';

export default function CommandDemo() {
  const [open, setOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setOpen(true)}>打开命令面板</button>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <CommandInput placeholder="搜索命令..." />
        <CommandList>
          <CommandEmpty>未找到匹配的命令</CommandEmpty>
          {/* 这里添加你的命令选项 */}
        </CommandList>
      </CommandDialog>
    </div>
  );
}

备注:内容来源于stack exchange,提问作者moth

火山引擎 最新活动