使用Prisma与NextJS创建记录时遭遇Hooks错误
基于Create-T3-App开发时Prisma创建记录的Hook问题解决
问题描述
使用Create-T3-App开发,通过含两个文本字段的网页表单插入Prisma记录时,遇到两类错误:
- 未处理运行时错误:
TypeError: hooks[lastArg] is not a function,报错点在.create()调用行 - React渲染错误:
Objects are not valid as a React child (found: [object Promise])
相关代码
index.tsx
import Head from "next/head"; import { api } from "~/utils/api"; import React, { FormEvent, ReactNode } from "react"; export default async function Home() { async function handleSubmit(e: FormEvent<HTMLFormElement>) { e.preventDefault(); const formData = new FormData(e.currentTarget); /* THE hooks[lastArg] is not a function ERROR SEEMS TO COME UP AT THE .create() on the following line: */ const itemAdd = await api.item.create({ data: { name: (e.currentTarget.elements.namedItem("name") as HTMLInputElement) .value, isbn: (e.currentTarget.elements.namedItem("isbn") as HTMLInputElement) .value, }, // include: { id: true }, // Returns all fields for all posts }); console.log(itemAdd.id); } return ( <> <Head> <title>lectura.io</title> <meta name="description" content="" /> <link rel="icon" href="/favicon.ico" /> </Head> <main className="flex min-h-screen flex-col items-center justify-center"> <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 "> <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]"> This is cool </h1> </div> <form onSubmit={handleSubmit}> <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 "> <div> name: <input type="text" name="name" className="border-2" /> </div> <div> isbn: <input type="text" name="isbn" placeholder="isbn" /> </div> <button type="submit">Submit</button> </div> </form> </main> </> ); }
item.ts(路由)
import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure, } from "~/server/api/trpc"; import { api } from "~/utils/api"; export const itemRouter = createTRPCRouter({ getById: publicProcedure.input(z.string()).query(({ ctx, input }) => { return ctx.db.item.findFirst({ where: { id: input, }, }); }), create: publicProcedure .input( z.object({ name: z.string(), isbn: z.string(), }), ) .mutation(async ({ ctx, input }) => { //const userId = ctx.session.user.id; try { const item = await ctx.db.item.create({ data: { name: input.name, isbn: input.isbn, }, }); return item; } catch (error) { console.log(error); } }), });
api.ts
import { httpBatchLink, loggerLink } from "@trpc/client"; import { createTRPCNext } from "@trpc/next"; import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; import superjson from "superjson"; import { type AppRouter } from "~/server/api/root"; const getBaseUrl = () => { if (typeof window !== "undefined") return ""; // browser should use relative url if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost }; /** A set of type-safe react-query hooks for your tRPC API. */ export const api = createTRPCNext<AppRouter>({ config() { return { transformer: superjson, links: [ loggerLink({ enabled: (opts) => process.env.NODE_ENV === "development" || (opts.direction === "down" && opts.result instanceof Error), }), httpBatchLink({ url: `${getBaseUrl()}/api/trpc`, }), ], }; }, ssr: false, }); export type RouterInputs = inferRouterInputs<AppRouter>; export type RouterOutputs = inferRouterOutputs<AppRouter>;
root.ts
import { exampleRouter } from "~/server/api/routers/example"; import { createTRPCRouter } from "~/server/api/trpc"; import { itemRouter } from "./routers/item"; /** * This is the primary router for your server. * All routers added in /api/routers should be manually added here. */ export const appRouter = createTRPCRouter({ example: exampleRouter, item: itemRouter, }); // export type definition of API export type AppRouter = typeof appRouter;
问题根源
- React组件不能是async函数:你把
Home组件声明为async function,导致组件返回Promise而非React元素,触发渲染错误。 - 错误调用tRPC mutation:
api.item.create是React Query hook生成器,必须在组件顶层调用获取mutation函数,不能直接在普通async函数里执行.create(),违反Hook规则导致运行时错误。
修复方案
1. 修改Home组件为普通函数
移除async声明,确保组件返回React元素:
export default function Home() { // ... 内部代码 }
2. 使用tRPC mutation hook处理表单提交
在组件顶层调用api.item.create.useMutation()获取mutation函数,通过mutate方法触发请求:
import Head from "next/head"; import { api } from "~/utils/api"; import React, { FormEvent } from "react"; export default function Home() { // 获取tRPC mutation函数,支持成功/失败回调 const createItem = api.item.create.useMutation({ onSuccess: (item) => { console.log(item.id); // 可选:重置表单或更新UI }, onError: (error) => { console.error("创建失败:", error); }, }); async function handleSubmit(e: FormEvent<HTMLFormElement>) { e.preventDefault(); const nameInput = e.currentTarget.elements.namedItem("name") as HTMLInputElement; const isbnInput = e.currentTarget.elements.namedItem("isbn") as HTMLInputElement; // 调用mutation提交数据 createItem.mutate({ name: nameInput.value, isbn: isbnInput.value, }); // 可选:重置表单 e.currentTarget.reset(); } return ( <> <Head> <title>lectura.io</title> <meta name="description" content="" /> <link rel="icon" href="/favicon.ico" /> </Head> <main className="flex min-h-screen flex-col items-center justify-center"> <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 "> <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]"> This is cool </h1> </div> <form onSubmit={handleSubmit}> <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 "> <div> name: <input type="text" name="name" className="border-2" /> </div> <div> isbn: <input type="text" name="isbn" placeholder="isbn" /> </div> {/* 添加加载状态禁用按钮 */} <button type="submit" disabled={createItem.isLoading}> {createItem.isLoading ? "提交中..." : "Submit"} </button> </div> </form> </main> </> ); }
3. 优化后端mutation(可选)
确保后端在出错时抛出错误,方便前端捕获处理:
create: publicProcedure .input(z.object({ name: z.string(), isbn: z.string() })) .mutation(async ({ ctx, input }) => { try { const item = await ctx.db.item.create({ data: { name: input.name, isbn: input.isbn }, }); return item; } catch (error) { console.error(error); // 抛出错误让前端捕获 throw new Error("创建记录失败"); } }),
内容的提问来源于stack exchange,提问作者mateoc15
相关产品推荐
相关产品推荐

