You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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;

问题根源

  1. React组件不能是async函数:你把Home组件声明为async function,导致组件返回Promise而非React元素,触发渲染错误。
  2. 错误调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 15:10:56