Next.js v13.4服务器组件加删除按钮报错,求组件拆分方案
问题描述
我开发了一个基于Next.js v13.4的应用,其中有一个Server Component(服务器组件)负责从数据库获取所有用户数据,并将每个用户展示在独立卡片中。我希望给每个卡片添加一个删除按钮,但为按钮添加点击事件监听器时出现以下错误:
Unhandled Runtime Error Error: Event handlers cannot be passed to Client Component props.
同时,尝试使用useRouter()时也报错:
Error: useRouter only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component
组件代码如下:
// import { useRouter } from "next/navigation"; import Link from "next/link"; import dbConnect from "../lib/dbConnect"; import User from "../models/User"; export default async function Home() { // const router = useRouter(); const data = await getData(); const users = data.props.users; const handleDelete = async (id: any) => { try { await fetch(`/api/users/${id}`, { method: "Delete", }); // router.push("/"); } catch (error) { console.log("error ===> ", error); } }; return ( <main className="main"> <h1>Home page</h1> {users ? ( <div className="users"> {users.map((user) => ( <div key={user._id}> <div className="card"> <h3 className="user-name">Name: {user.name}</h3> <p className="email">Email: {user.email}</p> <p className="email"> Password: {user.password} </p> <p className="email">Country: {user.country}</p> <button onClick={(_id) => handleDelete(_id)}>Delete</button> </div> </div> ))} <Link className="add-btn-link" href="/new"> <div className="add-btn"> <p>+</p> </div> </Link> </div> ) : ( <p>No users</p> )} </main> ); } async function getData() { await dbConnect(); /* find all the data in our database */ const result = await User.find({}); const users = result.map((doc) => { const user = doc.toObject(); user._id = user._id.toString(); return user; }); return { props: { users: users } }; }
错误截图:
解决方案
核心是将带交互逻辑的部分拆分到Client Component,保留原Home组件作为Server Component负责数据获取,兼顾服务器渲染优势与客户端交互能力。
1. 创建用户卡片客户端组件
新建components/UserCard.tsx文件,顶部添加"use client"标记,将卡片UI、删除逻辑和路由操作移至此处:
"use client"; import { useRouter } from "next/navigation"; interface UserProps { user: { _id: string; name: string; email: string; password: string; country: string; }; } export default function UserCard({ user }: UserProps) { const router = useRouter(); const handleDelete = async () => { try { await fetch(`/api/users/${user._id}`, { method: "DELETE", }); // 删除后刷新页面获取最新数据 router.refresh(); } catch (error) { console.log("error ===> ", error); } }; return ( <div className="card"> <h3 className="user-name">Name: {user.name}</h3> <p className="email">Email: {user.email}</p> <p className="email">Password: {user.password}</p> <p className="email">Country: {user.country}</p> <button onClick={handleDelete}>Delete</button> </div> ); }
2. 修改Home服务器组件
保留原数据获取逻辑,引入并使用UserCard组件,移除原有交互代码:
import Link from "next/link"; import dbConnect from "../lib/dbConnect"; import User from "../models/User"; import UserCard from "../components/UserCard"; export default async function Home() { const data = await getData(); const users = data.props.users; return ( <main className="main"> <h1>Home page</h1> {users ? ( <div className="users"> {users.map((user) => ( <div key={user._id}> <UserCard user={user} /> </div> ))} <Link className="add-btn-link" href="/new"> <div className="add-btn"> <p>+</p> </div> </Link> </div> ) : ( <p>No users</p> )} </main> ); } async function getData() { await dbConnect(); const result = await User.find({}); const users = result.map((doc) => { const user = doc.toObject(); user._id = user._id.toString(); return user; }); return { props: { users: users } }; }
关键要点
"use client":必须放在客户端组件文件最顶部,告知Next.js该组件需在客户端运行,支持事件监听、hooks(如useRouter)。- 服务器组件专注数据获取:原Home组件保持为服务器组件,无需添加
"use client",可直接在服务器端查询数据库,减少客户端请求开销。 router.refresh():删除用户后调用此方法刷新页面,获取最新用户列表,比router.push("/")更高效。
内容的提问来源于stack exchange,提问作者in43sh
相关产品推荐
相关产品推荐

