# LLM docs ### Components ```tsx // Server-rendered page (used to optimize speed and SEO) export default async function ProjectPage({ params }: { params: Promise<{ projectId: string }>}) { const { projectId } = await params // Fetch with oRPC const project = await client.projects.find({ projectId }) return ... } export default async function ProjectsListPage({ searchParams }: { searchParams: Promise<{ limit: number; cursor: number; }>}) { const { limit, cursor } = await searchParams const projects = await client.projects.list({ limit, cursor }) return ... } ``` ```tsx // Client-side components only when needed export function ProjectCard({ projectId }: { projectId: string }) { const { data, isPending, error, refetch } = useQuery( orpc.project.find.queryOptions({ input: { projectId }}) ) if (error) return ( <ErrorState title="Error fetching project" onRetry={refetch}> Something went wrong when fetching project with id: {projectId} </ErrorState> ) if (isPending) return <ProjectCardSkeleton /> return <Card>...</Card> } ``` ```tsx export function ListProjects() { // Use nuqs for handling query strings for search, filter, etc const [{ limit, cursor }, setPagination] = useQueryStates( { limit: parseAsInteger.withDefault(10), cursor: parseAsInteger.withDefault(0) } ) // Use infinite query to easily paginate results const { data, refetch, fetchNextPage, hasNextPage, error } = useSuspenseInfiniteQuery( orpc.project.list.infiniteOptions({ input: cursor => ({ cursor, limit }), getNextPageParam: lastPage => lastPage.length === limit ? lastPage.at(-1)?.id : null, initialPageParam: cursor, }) ) if (error) return ( <ErrorState title="Error fetching projects" onRetry={refetch}> Something went wrong when listing projects </ErrorState> ) // // Render Grid return ( <Grid columns={[1, 1, 2]} data={data.pages.flat()} renderItem={project => <ProjectCard key={project.id} projectId={project.id} />} /> ) // OR Render Table return ( <Table> <TableHead> <TableRow> <TableHeader>ID</TableHeader> <TableHeader>Name</TableHeader> <TableHeader>Description</TableHeader> <TableHeader>Image</TableHeader> </TableRow> </TableHead> <TableBody> {data.pages.flatMap((page, i) => page.map(project => ( <TableRow key={`${project.id}-${i}`}> <TableCell>{project.id}</TableCell> <TableCell>{project.name}</TableCell> <TableCell>{project.description}</TableCell> <TableCell>{project.imageUrl}</TableCell> </TableRow> )), )} </TableBody> <TableFooter> <TableRow> <TableCell colSpan={4}> <button type="button" onClick={() => fetchNextPage()} disabled={!hasNextPage} > Load more </button> <button type="button" onClick={() => refetch()}> Refresh </button> </TableCell> </TableRow> </TableFooter> </Table> ) } ``` #### Form components ```tsx export function CreateProjectForm() { // Always useForm directly in the form component to locate the form logic close to the rendered components const form = useForm<z.infer<typeof projectInputSchema>>({ resolver: zodResolver(projectInputSchema), defaultValues: {} }) const { mutateAsync, isPending } = useMutation( orpc.project.create.mutationOptions({ onSuccess() { queryClient.invalidateQueries({ queryKey: orpc.project.key() }) }, onError(error) { console.error(error) toast.error(error.message) }, }), ) function onSubmit(values: z.infer<typeof projectInputSchema>) { return mutateAsync(values) } const { isSubmitting } = form.formState return ( <Form> <form onSubmit={form.handleSubmit(onSubmit)}> {/* Prefer the slighty more redundant way of using FormField directly rather than wrapping components for greater clarity (complex re-usable form elements with custom logic are fine to wrap) */} <FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormLabel>Project Name</FormLabel> <FormControl> <Input placeholder="Project Name" {...field} /> </FormControl> <FormDescription>This is the project display name.</FormDescription> <FormMessage /> </FormItem> )} /> ... <Button type="submit" isLoading={isSubmitting} loadingText="Creating..." icon={PlusIcon}> Create Project </Button> </form> </Form> ) } ``` ### oRPC Router ```ts import { listProjects, ... } from "./projects" export const router = { ... project: { list: listProjects, create: createProject, find: findProject, update: updateProject, }, ... } ``` ```ts export const createProject = authed // or pub for public routes .route({ method: 'POST', path: '/projects', summary: 'Create a project', tags: ['Projects'], }) .input(ProjectInputSchema) // Validates input .output(ProjectSchema) // Validates returned data .handler(async ({ input, context }) => { return context.db.projects.create(input, context.user) }) export const listProjects = pub .route({ method: 'GET', path: '/projects', summary: 'List all projects', tags: ['Projects'], }) .input( z.object({ limit: z.number().int().min(1).max(100).default(10), cursor: z.number().int().min(0).default(0), }), ) .output(z.array(ProjectSchema)) .handler(async ({ input, context }) => { return context.db.projects.select() .orderBy(asc(projects.updatedAt)) .limit(input.limit) .offset(input.cursor); }) ```