Nextjs 14 Form Submit with tRPC

Introdution

In this article,we are gonna enumerate some kinds of form submitting with trpc concretely, globally in detail. If need, please see 《NextJs 14 从入门到精通之tRPC快速入门》

Prepare trpc base enviroment

Form component typically is used in Client Component, you can submmit with http request or call server action directly. So we start along this thought

We need to create tRPC appRouter at first

bun add @trpc/server@next @trpc/client@next

ts 复制代码
import { initTRPC } from '@trpc/server';
import { db } from '@/server/db';

export const createTRPCContext = async (opts: {
  req?: NextRequest;
}) => {
  // get session
  const session = { user: {userId: 'xxx'}, sessionId: 'xxxx' };

  return {
    db,
    session,
    ...opts,
  };
};

const t = initTRPC.context<typeof createTRPCContext>().create()

const publicProcedure = t.procedure;

const protectedProcedure = t.procedure.use(({ ctx, next }) => {
    if (!ctx.session || !ctx.session.userId) {
      throw new TRPCError({ code: 'UNAUTHORIZED' });
    }

    return next({
      ctx: {
        // infers the `session` as non-nullable
        session: {
          ...ctx.session,
          userId: ctx.session.userId,
        },
      },
    });
});

export const appRouter = t.router({
    createUser: protectedProcedure
        .input(z.object({
          name: z.string(),
          password: z.string()
        }))
        .mutation(async ({ ctx, input }) => {
            const { userId } = ctx;
            const data = await ctx.db.user.create({
              data: {...input, userId }
            })
            return { status: 200, data, message: 'success' }
        })
})

Http Request with tRPC

Firstly, make tRPC appRouter match NextJs api-router

ts 复制代码
// src/app/api/trpc/[...trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/api/root';
import { createTRPCContext } from '@/server/api/trpc';

const handler = (req: NextRequest) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: () => createTRPCContext(),
    onError: ({ path, error }) => {
      console.log('------', error);
    },
  });

export { handler as GET, handler as POST };

Secondly, Create tRPC Client with react-query

bun add @trpc/react-query@next @tanstack/react-query

tsx 复制代码
// tRPCReact.ts
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createTRPCReact } from '@trpc/react-query';
import { unstable_httpBatchStreamLink } from '@trpc/client';
import { useState } from 'react';

const createQueryClient = () => new QueryClient();

let clientQueryClientSingleton: QueryClient | undefined = undefined;

const getQueryClient = () => {
  if (typeof window === 'undefined') {
    return createQueryClient();
  }
  return (clientQueryClientSingleton ??= createQueryClient());
};

export const api = createTRPCReact<AppRouter>();

export function TRPCReactProvider(props: { children: React.ReactNode }) {
  const queryClient = getQueryClient();

  const [trpcClient] = useState(() =>
    api.createClient({
      links: [
        unstable_httpBatchStreamLink({
          url: 'http:localhost:3000/api/trpc',
        }),
      ],
    })
  );

  return (
    <QueryClientProvider client={queryClient}>
      <api.Provider client={trpcClient} queryClient={queryClient}>
        {props.children}
      </api.Provider>
    </QueryClientProvider>
  );
}

Thirdly, used in form component

  • To use react-query, your component must be wrapped with TRPCReactProvider before.
ts 复制代码
export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body>
                <TRPCReactProvider>{children}</TRPCReactProvider>
            </body>
        </html>
    )
}
  • In form component.
tsx 复制代码
'use client';
import { api } from 'tRPCReact';
export function Form() {
    const create = api.createUser.useMutation({
      onSuccess: () => {}
      onError: (e) => {}
    })
    
    return (
        <form>
            {/* ... */}
            <button onClick={() => create.mutate({
                name, password
            })} >Submit</button>
        </form>
    )
}

Server Action with tRPC's createCaller

NextJs 14 supports calling server action directly for form submmiting. And similarly,tRPC supports server side calls. so we can do following this:

  • Go back to the file of appRouter, export default createCallerFactory
ts 复制代码
// ...
const t = initTRPC.context<typeof createTRPCContext>().create()
// ...

export const createCallerFactory = t.createCallerFactory;
  • Create trpc caller
ts 复制代码
// tRPCServer
const createCaller = createCallerFactory(appRouter);
export const api = createCaller(() => createTRPCContext());
  • Used in form component
tsx 复制代码
'use client';
import { api } from 'tRPCServer';

export function Form() {
    const create = async (formData, FormData) {
        'use server'
        try {
            api.createUser(formData)
        } catch (e) {}
    }

    return (
        <form action={create}>
            {/* ... */}
        </form>
    )
}

Server Action with tRPC's experimental_caller

This is an experimental feature, we should not better to use it. it's based on top of procedure, likely a middleware, finally return a new procedure.

And we will not use the previous defined appRouter. we should re-write the inner logic.

so we can do by following:

  • Export default createCallerFactory
ts 复制代码
// ....
const t = initTRPC.context<typeof createTRPCContext>().create();
// ....
// experimental: form action
export const createActionCaller = protectedProcedure.experimental_caller;
  • Create a file for createActionCaller
ts 复制代码
// tRPCAction
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir';

export const tRPCAction = createActionCaller(
  experimental_nextAppDirCaller({
    createContext: () => createTRPCContext()
  })
);
  • Define a Server Action as a file and write business logic
ts 复制代码
'use server';
import { tRPCAction } from 'tRPCAction';
import { z } from 'zod';

export const createUserAction = tRPCAction
        .input(z.object({
          name: z.string(),
          password: z.string()
        }))
        .mutation(async ({ ctx, input }) => {
            const { userId } = ctx;
            const data = await ctx.db.user.create({
              data: {...input, userId }
            })
            return { status: 200, data, message: 'success' }
        })
  • Used in form component
tsx 复制代码
'use client';
import { createUserAction } from 'actions';

export function Form() {
    return (
        <form action={createUserAction}>
            {/* ... */}
        </form>
    )
}

Summarize

In Next.js 14, using server Actions to submit a form and call tRPC's createCaller is a straightforward way. Both offer different ways to invoke server-side code, depending on your needs and development style. Here is an explanation of the two ways:

  • createCaller is a way to call the server-side tRPC endpoint directly. It is suitable for situations where you need to call the tRPC endpoint directly in server-side code, such as during server Actions or other server-side processing.

  • experimental_caller provides an experimental method for calling tRPC methods on the server side. It is a more straightforward API, but because it is experimental, it may change in future releases

In fact, I suggest to use createCaller, because this way is used directly and once appRouter logic is defined. we can use server side call and server action.

相关推荐
陈随易1 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·后端·程序员
SoaringHeart2 小时前
Flutter进阶:基于 EasyRefresh 的下拉刷新封装 n_easy_refresh_mixin.dart
前端·flutter
IT_陈寒4 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
子兮曰4 小时前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
竹林8185 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
用户8356290780515 小时前
Python 实现 PDF 文件加密与解密方法
后端·python
小满zs5 小时前
Go语言第二章(小无相功)
后端·go
用户8356290780515 小时前
使用 Python 冻结与拆分 Excel 窗格教程
后端·python
karry_k5 小时前
MyBatis批量insert-select踩坑:useGeneratedKeys=true 可能让PostgreSQL返回大量插入结果
java·后端
妙码生花5 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go