Skip to content

Commit 42a485a

Browse files
feat: trpc prefetching (#1932)
1 parent a1a4b87 commit 42a485a

15 files changed

+487
-425
lines changed

.changeset/spotty-donkeys-admire.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-t3-app": minor
3+
---
4+
5+
feat: add trpc rsc prefetching

cli/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@
6969
"@prisma/adapter-planetscale": "^5.14.0",
7070
"@prisma/client": "^5.14.0",
7171
"@t3-oss/env-nextjs": "^0.10.1",
72-
"@tanstack/react-query": "^5.39.0",
73-
"@trpc/client": "11.0.0-rc.377",
74-
"@trpc/next": "11.0.0-rc.377",
75-
"@trpc/react-query": "11.0.0-rc.377",
76-
"@trpc/server": "11.0.0-rc.377",
72+
"@tanstack/react-query": "^5.49.2",
73+
"@trpc/client": "11.0.0-rc.441",
74+
"@trpc/next": "11.0.0-rc.441",
75+
"@trpc/react-query": "11.0.0-rc.441",
76+
"@trpc/server": "11.0.0-rc.441",
7777
"@types/fs-extra": "^11.0.4",
7878
"@types/gradient-string": "^1.1.6",
7979
"@types/node": "^20.12.12",

cli/src/installers/trpc.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,13 @@ export const trpcInstaller: Installer = ({
106106
path.join(
107107
extrasDir,
108108
"src/app/_components",
109-
packages?.tailwind.inUse ? "create-post-tw.tsx" : "create-post.tsx"
109+
packages?.tailwind.inUse ? "post-tw.tsx" : "post.tsx"
110110
),
111-
path.join(projectDir, "src/app/_components/create-post.tsx"),
111+
path.join(projectDir, "src/app/_components/post.tsx"),
112+
],
113+
[
114+
path.join(extrasDir, "src/trpc/query-client.ts"),
115+
path.join(projectDir, "src/trpc/query-client.ts"),
112116
]
113117
);
114118
} else {

cli/template/extras/src/app/_components/create-post-tw.tsx

-43
This file was deleted.

cli/template/extras/src/app/_components/create-post.tsx

-44
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
5+
import { api } from "~/trpc/react";
6+
7+
export function LatestPost() {
8+
const [latestPost] = api.post.getLatest.useSuspenseQuery();
9+
10+
const utils = api.useUtils();
11+
const [name, setName] = useState("");
12+
const createPost = api.post.create.useMutation({
13+
onSuccess: async () => {
14+
await utils.post.invalidate();
15+
setName("");
16+
},
17+
});
18+
19+
return (
20+
<div className="w-full max-w-xs">
21+
{latestPost ? (
22+
<p className="truncate">Your most recent post: {latestPost.name}</p>
23+
) : (
24+
<p>You have no posts yet.</p>
25+
)}
26+
<form
27+
onSubmit={(e) => {
28+
e.preventDefault();
29+
createPost.mutate({ name });
30+
}}
31+
className="flex flex-col gap-2"
32+
>
33+
<input
34+
type="text"
35+
placeholder="Title"
36+
value={name}
37+
onChange={(e) => setName(e.target.value)}
38+
className="w-full rounded-full px-4 py-2 text-black"
39+
/>
40+
<button
41+
type="submit"
42+
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
43+
disabled={createPost.isPending}
44+
>
45+
{createPost.isPending ? "Submitting..." : "Submit"}
46+
</button>
47+
</form>
48+
</div>
49+
);
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
5+
import { api } from "~/trpc/react";
6+
import styles from "../index.module.css";
7+
8+
export function LatestPost() {
9+
const [latestPost] = api.post.getLatest.useSuspenseQuery();
10+
11+
const utils = api.useUtils();
12+
const [name, setName] = useState("");
13+
const createPost = api.post.create.useMutation({
14+
onSuccess: async () => {
15+
await utils.post.invalidate();
16+
setName("");
17+
},
18+
});
19+
20+
return (
21+
<div className={styles.showcaseContainer}>
22+
{latestPost ? (
23+
<p className={styles.showcaseText}>
24+
Your most recent post: {latestPost.name}
25+
</p>
26+
) : (
27+
<p className={styles.showcaseText}>You have no posts yet.</p>
28+
)}
29+
30+
<form
31+
onSubmit={(e) => {
32+
e.preventDefault();
33+
createPost.mutate({ name });
34+
}}
35+
className={styles.form}
36+
>
37+
<input
38+
type="text"
39+
placeholder="Title"
40+
value={name}
41+
onChange={(e) => setName(e.target.value)}
42+
className={styles.input}
43+
/>
44+
<button
45+
type="submit"
46+
className={styles.submitButton}
47+
disabled={createPost.isPending}
48+
>
49+
{createPost.isPending ? "Submitting..." : "Submit"}
50+
</button>
51+
</form>
52+
</div>
53+
);
54+
}
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,67 @@
11
import Link from "next/link";
22

3-
import { CreatePost } from "~/app/_components/create-post";
3+
import { LatestPost } from "~/app/_components/post";
44
import { getServerAuthSession } from "~/server/auth";
5-
import { api } from "~/trpc/server";
5+
import { api, HydrateClient } from "~/trpc/server";
66

77
export default async function Home() {
88
const hello = await api.post.hello({ text: "from tRPC" });
99
const session = await getServerAuthSession();
1010

11-
return (
12-
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
13-
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 ">
14-
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
15-
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
16-
</h1>
17-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
18-
<Link
19-
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
20-
href="https://create.t3.gg/en/usage/first-steps"
21-
target="_blank"
22-
>
23-
<h3 className="text-2xl font-bold">First Steps →</h3>
24-
<div className="text-lg">
25-
Just the basics - Everything you need to know to set up your
26-
database and authentication.
27-
</div>
28-
</Link>
29-
<Link
30-
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
31-
href="https://create.t3.gg/en/introduction"
32-
target="_blank"
33-
>
34-
<h3 className="text-2xl font-bold">Documentation →</h3>
35-
<div className="text-lg">
36-
Learn more about Create T3 App, the libraries it uses, and how to
37-
deploy it.
38-
</div>
39-
</Link>
40-
</div>
41-
<div className="flex flex-col items-center gap-2">
42-
<p className="text-2xl text-white">
43-
{hello ? hello.greeting : "Loading tRPC query..."}
44-
</p>
11+
void api.post.getLatest.prefetch();
4512

46-
<div className="flex flex-col items-center justify-center gap-4">
47-
<p className="text-center text-2xl text-white">
48-
{session && <span>Logged in as {session.user?.name}</span>}
49-
</p>
13+
return (
14+
<HydrateClient>
15+
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
16+
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 ">
17+
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
18+
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
19+
</h1>
20+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
5021
<Link
51-
href={session ? "/api/auth/signout" : "/api/auth/signin"}
52-
className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
22+
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
23+
href="https://create.t3.gg/en/usage/first-steps"
24+
target="_blank"
5325
>
54-
{session ? "Sign out" : "Sign in"}
26+
<h3 className="text-2xl font-bold">First Steps →</h3>
27+
<div className="text-lg">
28+
Just the basics - Everything you need to know to set up your
29+
database and authentication.
30+
</div>
31+
</Link>
32+
<Link
33+
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
34+
href="https://create.t3.gg/en/introduction"
35+
target="_blank"
36+
>
37+
<h3 className="text-2xl font-bold">Documentation →</h3>
38+
<div className="text-lg">
39+
Learn more about Create T3 App, the libraries it uses, and how
40+
to deploy it.
41+
</div>
5542
</Link>
5643
</div>
57-
</div>
58-
59-
<CrudShowcase />
60-
</div>
61-
</main>
62-
);
63-
}
64-
65-
async function CrudShowcase() {
66-
const session = await getServerAuthSession();
67-
if (!session?.user) return null;
68-
69-
const latestPost = await api.post.getLatest();
44+
<div className="flex flex-col items-center gap-2">
45+
<p className="text-2xl text-white">
46+
{hello ? hello.greeting : "Loading tRPC query..."}
47+
</p>
7048

71-
return (
72-
<div className="w-full max-w-xs">
73-
{latestPost ? (
74-
<p className="truncate">Your most recent post: {latestPost.name}</p>
75-
) : (
76-
<p>You have no posts yet.</p>
77-
)}
49+
<div className="flex flex-col items-center justify-center gap-4">
50+
<p className="text-center text-2xl text-white">
51+
{session && <span>Logged in as {session.user?.name}</span>}
52+
</p>
53+
<Link
54+
href={session ? "/api/auth/signout" : "/api/auth/signin"}
55+
className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
56+
>
57+
{session ? "Sign out" : "Sign in"}
58+
</Link>
59+
</div>
60+
</div>
7861

79-
<CreatePost />
80-
</div>
62+
{session?.user && <LatestPost />}
63+
</div>
64+
</main>
65+
</HydrateClient>
8166
);
8267
}

0 commit comments

Comments
 (0)