This repository was archived by the owner on Feb 11, 2025. It is now read-only.
forked from trpc/trpc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(server): add next.js
experimental_caller()
-adapter (trpc#5589)
- Loading branch information
Showing
52 changed files
with
885 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ const config = { | |
'unstable-*', | ||
'script', | ||
'URL', | ||
'next-app-dir', | ||
], | ||
}, | ||
], | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
examples/.experimental/next-app-dir/src/app/posts/AddPostForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
'use client'; | ||
|
||
import toast from 'react-hot-toast'; | ||
import { addPost } from './_data'; | ||
import { addPostSchema } from './_data.schema'; | ||
import { Form, useZodForm } from './_lib/Form'; | ||
|
||
export function AddPostForm() { | ||
const form = useZodForm({ | ||
schema: addPostSchema, | ||
defaultValues: { | ||
title: 'hello world', | ||
content: 'this is a test post', | ||
}, | ||
}); | ||
|
||
return ( | ||
<Form | ||
form={form} | ||
handleSubmit={async (values) => { | ||
// ^? | ||
const promise = addPost(values); | ||
return toast | ||
.promise(promise, { | ||
loading: 'Adding post...', | ||
success: 'Post added!', | ||
error: (error) => 'Failed to add post: ' + error.message, | ||
}) | ||
.catch((error) => { | ||
console.warn('Failed to add post', error); | ||
}); | ||
}} | ||
> | ||
<div> | ||
<input | ||
type="text" | ||
{...form.register('title')} | ||
defaultValue={form.control._defaultValues.title} | ||
/> | ||
{form.formState.errors.title && ( | ||
<div>Invalid title: {form.formState.errors.title.message}</div> | ||
)} | ||
</div> | ||
|
||
<div> | ||
<input | ||
type="text" | ||
{...form.register('content')} | ||
defaultValue={form.control._defaultValues.content} | ||
/> | ||
{form.formState.errors.content && ( | ||
<div>Invalid content: {form.formState.errors.content.message}</div> | ||
)} | ||
</div> | ||
<button type="submit" disabled={form.formState.isSubmitting}> | ||
Add post | ||
</button> | ||
</Form> | ||
); | ||
} |
17 changes: 17 additions & 0 deletions
17
examples/.experimental/next-app-dir/src/app/posts/[id]/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { postById } from '../_data'; | ||
|
||
export default async function Page(props: { | ||
params: { | ||
id: string; | ||
}; | ||
}) { | ||
const post = await postById(props.params); | ||
|
||
return ( | ||
<div> | ||
<h1>{post.title}</h1> | ||
|
||
<p>{post.content}</p> | ||
</div> | ||
); | ||
} |
11 changes: 11 additions & 0 deletions
11
examples/.experimental/next-app-dir/src/app/posts/_data.schema.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { z } from 'zod'; | ||
|
||
export const postSchema = z.object({ | ||
id: z.string(), | ||
title: z.string().min(1), | ||
content: z.string().min(1), | ||
}); | ||
|
||
export type Post = z.infer<typeof postSchema>; | ||
|
||
export const addPostSchema = postSchema.omit({ id: true }); |
53 changes: 53 additions & 0 deletions
53
examples/.experimental/next-app-dir/src/app/posts/_data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use server'; | ||
|
||
import { revalidatePath } from 'next/cache'; | ||
import { RedirectType } from 'next/navigation'; | ||
import { z } from 'zod'; | ||
import { addPostSchema, type Post } from './_data.schema'; | ||
import { db } from './_lib/db'; | ||
import { nextProc, notFound, redirect } from './_lib/trpc'; | ||
|
||
export const addPost = nextProc | ||
.input( | ||
addPostSchema.superRefine(async (it, ctx) => { | ||
const posts = await db.listPosts(); | ||
console.log('posts in db', posts); | ||
if (posts.some((post) => post.title === it.title)) { | ||
ctx.addIssue({ | ||
code: 'custom', | ||
message: 'Title already exists', | ||
path: ['title'], | ||
}); | ||
} | ||
}), | ||
) | ||
.mutation(async (opts) => { | ||
await new Promise((resolve) => setTimeout(resolve, 300)); | ||
const post: Post = { | ||
...opts.input, | ||
id: `${Math.random()}`, | ||
}; | ||
|
||
await db.addPost(post); | ||
revalidatePath('/'); | ||
redirect(`/posts/${post.id}`, RedirectType.push); | ||
}); | ||
|
||
export const listPosts = nextProc.query(async () => { | ||
return await db.listPosts(); | ||
}); | ||
|
||
export const postById = nextProc | ||
.input( | ||
z.object({ | ||
id: z.string(), | ||
}), | ||
) | ||
.query(async (opts) => { | ||
const post = await db.getPost(opts.input.id); | ||
if (!post) { | ||
console.warn(`Post with id ${opts.input.id} not found`); | ||
notFound(); | ||
} | ||
return post; | ||
}); |
50 changes: 50 additions & 0 deletions
50
examples/.experimental/next-app-dir/src/app/posts/_lib/Form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
'use client'; | ||
|
||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form'; | ||
import { FormProvider, useForm } from 'react-hook-form'; | ||
import toast from 'react-hot-toast'; | ||
import type { z } from 'zod'; | ||
|
||
/** | ||
* Reusable hook for zod + react-hook-form | ||
*/ | ||
export function useZodForm<TInput extends FieldValues>( | ||
props: Omit<UseFormProps<TInput>, 'resolver'> & { | ||
schema: z.ZodType<any, any, TInput>; | ||
}, | ||
) { | ||
const form = useForm<TInput>({ | ||
...props, | ||
resolver: zodResolver(props.schema, undefined, { | ||
rawValues: true, | ||
}), | ||
}); | ||
|
||
return form; | ||
} | ||
|
||
export const Form = <TInput extends FieldValues = never>(props: { | ||
children: React.ReactNode; | ||
form: UseFormReturn<TInput>; | ||
handleSubmit: (values: NoInfer<TInput>) => Promise<unknown>; | ||
}) => { | ||
return ( | ||
<FormProvider {...props.form}> | ||
<form | ||
onSubmit={(event) => { | ||
return props.form.handleSubmit(async (values) => { | ||
try { | ||
await props.handleSubmit(values); | ||
} catch (error) { | ||
console.error('Uncaught error in form', error); | ||
toast.error('Failed to submit form'); | ||
} | ||
})(event); | ||
}} | ||
> | ||
{props.children} | ||
</form> | ||
</FormProvider> | ||
); | ||
}; |
39 changes: 39 additions & 0 deletions
39
examples/.experimental/next-app-dir/src/app/posts/_lib/db.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { type Post } from '../_data.schema'; | ||
|
||
interface PostDb { | ||
getPost: (id: string) => Promise<Post | undefined>; | ||
addPost: (post: Post) => Promise<void>; | ||
listPosts: () => Promise<Post[]>; | ||
} | ||
const mockDb = (): PostDb => { | ||
console.log('⚙️ Using mock DB'); | ||
const posts: Post[] = [ | ||
{ | ||
id: '1', | ||
title: 'Hello world', | ||
content: 'This is a test post', | ||
}, | ||
{ | ||
id: '2', | ||
title: 'Second post', | ||
content: 'This is another test post', | ||
}, | ||
]; | ||
return { | ||
getPost: async (id) => { | ||
return posts.find((post) => post.id === id); | ||
}, | ||
addPost: async (post) => { | ||
posts.push(post); | ||
}, | ||
listPosts: async () => { | ||
return posts; | ||
}, | ||
}; | ||
}; | ||
const pgDb = (): PostDb => { | ||
console.log('🚀 Using PG store'); | ||
|
||
throw new Error('Not implemented'); | ||
}; | ||
export const db = process.env.POSTGRES_URL ? pgDb() : mockDb(); |
24 changes: 24 additions & 0 deletions
24
examples/.experimental/next-app-dir/src/app/posts/_lib/trpc.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { initTRPC } from '@trpc/server'; | ||
import { experimental_nextAppDirCaller } from '@trpc/server/adapters/next-app-dir'; | ||
|
||
const t = initTRPC.create(); | ||
|
||
export const nextProc = t.procedure | ||
.use(async function artificialDelay(opts) { | ||
if (t._config.isDev) { | ||
// random number between 100 and 500ms | ||
const waitMs = Math.floor(Math.random() * 400) + 100; | ||
await new Promise((resolve) => setTimeout(resolve, waitMs)); | ||
} | ||
return opts.next(); | ||
}) | ||
.experimental_caller( | ||
experimental_nextAppDirCaller({ | ||
normalizeFormData: true, | ||
}), | ||
); | ||
|
||
export { | ||
experimental_notFound as notFound, | ||
experimental_redirect as redirect, | ||
} from '@trpc/server/adapters/next-app-dir'; |
26 changes: 26 additions & 0 deletions
26
examples/.experimental/next-app-dir/src/app/posts/layout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
'use client'; | ||
|
||
import { useEffect } from 'react'; | ||
import toast from 'react-hot-toast'; | ||
|
||
let toasted = false; | ||
export default function PostLayout(props: { children: React.ReactNode }) { | ||
useEffect(() => { | ||
if (process.env.NODE_ENV === 'development') { | ||
const timeout = setTimeout(() => { | ||
if (toasted) return; | ||
toasted = true; | ||
toast( | ||
'Please note that we have an artificial delay on the server functions to simulate real-world conditions.', | ||
{ | ||
icon: '🐌', | ||
}, | ||
); | ||
}, 1); | ||
return () => { | ||
clearTimeout(timeout); | ||
}; | ||
} | ||
}, []); | ||
return props.children; | ||
} |
23 changes: 23 additions & 0 deletions
23
examples/.experimental/next-app-dir/src/app/posts/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { listPosts } from './_data'; | ||
import { AddPostForm } from './AddPostForm'; | ||
|
||
export default async function Home() { | ||
const posts = await listPosts(); | ||
|
||
return ( | ||
<div> | ||
<h1>All posts</h1> | ||
|
||
<ul> | ||
{posts.map((post) => ( | ||
<li key={post.id}> | ||
<a href={`/posts/${post.id}`}>{post.title}</a> | ||
</li> | ||
))} | ||
</ul> | ||
|
||
<h2>Add post</h2> | ||
<AddPostForm /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.