Alex Gomes
Alex Gomes8mo ago

onSuccess invalidate

I'm trying to follow along with Theo's T3 tutorial using the latest Next version and the app router. The "setInput("")" and the invalidation don't seem to be working inside of onSuccess, the console.log is being called, what am I missing? Is this supposed to work?
"use client";

import { useUser } from "@clerk/nextjs";
import Image from "next/image";
import { Loader } from "./loader";
import { api } from "~/trpc/react";
import { useState } from "react";

export function CreatePost() {
const { user } = useUser();
const utils = api.useUtils();
const [input, setInput] = useState<string>("");
const { mutate, isLoading } = api.post.create.useMutation({
onSuccess: () => {
console.log("THIS IS BEING LOGGED");
// INPUT IS NO BEING RESET
setInput("");
// PAGE IS NOT SHOWING FRESH DATA
void utils.post.getAll.invalidate();
},
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({ content: input });
};

if (!user) return <Loader />;

return (
<div className="flex flex-1 items-center gap-3">
<form onSubmit={handleSubmit} className="flex grow">
<input
onChange={(e) => setInput(e.target.value)}
placeholder="Start typing something..."
type="text"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
>
{isLoading ? <Loader size={18} /> : "Post"}
</button>
</form>
</div>
);
}
"use client";

import { useUser } from "@clerk/nextjs";
import Image from "next/image";
import { Loader } from "./loader";
import { api } from "~/trpc/react";
import { useState } from "react";

export function CreatePost() {
const { user } = useUser();
const utils = api.useUtils();
const [input, setInput] = useState<string>("");
const { mutate, isLoading } = api.post.create.useMutation({
onSuccess: () => {
console.log("THIS IS BEING LOGGED");
// INPUT IS NO BEING RESET
setInput("");
// PAGE IS NOT SHOWING FRESH DATA
void utils.post.getAll.invalidate();
},
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({ content: input });
};

if (!user) return <Loader />;

return (
<div className="flex flex-1 items-center gap-3">
<form onSubmit={handleSubmit} className="flex grow">
<input
onChange={(e) => setInput(e.target.value)}
placeholder="Start typing something..."
type="text"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
>
{isLoading ? <Loader size={18} /> : "Post"}
</button>
</form>
</div>
);
}
7 Replies
Alex Gomes
Alex Gomes8mo ago
dependencies:
"dependencies": {
"@clerk/nextjs": "^4.27.2",
"@prisma/client": "^5.6.0",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"date-fns": "^2.30.0",
"next": "^14.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.22.4"
},
"dependencies": {
"@clerk/nextjs": "^4.27.2",
"@prisma/client": "^5.6.0",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"date-fns": "^2.30.0",
"next": "^14.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^3.22.4"
},
Krishna
Krishna8mo ago
in the input component you are not setting the value property. you need
<input
value={input}
...
/>
<input
value={input}
...
/>
Alex Gomes
Alex Gomes8mo ago
Ahhh yeah this explains why the input wasn't being cleared, but my posts aren't updated, I need to manually refresh the page for my new post to appear
Krishna
Krishna8mo ago
what posts? will need more context...
Alex Gomes
Alex Gomes8mo ago
The CreatePost component creates a post when you submit the form:
const { mutate, isLoading } = api.post.create.useMutation({
onSuccess: () => {
console.log("THIS IS BEING LOGGED");
// INPUT IS NO BEING RESET
setInput("");
// PAGE IS NOT SHOWING FRESH DATA
void utils.post.getAll.invalidate();
},
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({ content: input });
};
const { mutate, isLoading } = api.post.create.useMutation({
onSuccess: () => {
console.log("THIS IS BEING LOGGED");
// INPUT IS NO BEING RESET
setInput("");
// PAGE IS NOT SHOWING FRESH DATA
void utils.post.getAll.invalidate();
},
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({ content: input });
};
export const postRouter = createTRPCRouter({
create: privateProcedure
.input(z.object({ content: z.string().min(1).max(255) }))
.mutation(async ({ ctx, input }) => {
const authorId = ctx.user.id;

const { success } = await rateLimit.limit(authorId);
if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });

const post = await ctx.db.post.create({
data: { authorId, content: input.content },
select: { authorId: true, content: true, id: true },
});
return post;
}),
getAll: publicProcedure.query(async ({ ctx }) => {
const posts = await ctx.db.post.findMany({
take: 100,
orderBy: { createdAt: "desc" },
});
const users = (
await clerkClient.users.getUserList({
userId: posts.map((post) => post.authorId),
limit: 100,
})
).map(filterUserForClient);
return posts.map((post) => {
const author = users.find((user) => user.id === post.authorId);
if (!author)
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Author not found",
});
return {
post,
author,
};
});
}),
});
export const postRouter = createTRPCRouter({
create: privateProcedure
.input(z.object({ content: z.string().min(1).max(255) }))
.mutation(async ({ ctx, input }) => {
const authorId = ctx.user.id;

const { success } = await rateLimit.limit(authorId);
if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });

const post = await ctx.db.post.create({
data: { authorId, content: input.content },
select: { authorId: true, content: true, id: true },
});
return post;
}),
getAll: publicProcedure.query(async ({ ctx }) => {
const posts = await ctx.db.post.findMany({
take: 100,
orderBy: { createdAt: "desc" },
});
const users = (
await clerkClient.users.getUserList({
userId: posts.map((post) => post.authorId),
limit: 100,
})
).map(filterUserForClient);
return posts.map((post) => {
const author = users.find((user) => user.id === post.authorId);
if (!author)
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Author not found",
});
return {
post,
author,
};
});
}),
});
then onSuccess in useMutation calls the invalidate on post.getAll which I assume should cause the getAll to fetch fresh posts
import { api } from "~/trpc/server";
import PostView from "../_components/post-view";

export default async function Home() {
const allPosts = await api.post.getAll.query();
return (
<main className="text-white">
<ul className="flex flex-col gap-2 divide-y divide-slate-700">
{allPosts.map((post) => (
<PostView postWithAuthor={post} key={post.post.id} />
))}
</ul>
</main>
);
}
import { api } from "~/trpc/server";
import PostView from "../_components/post-view";

export default async function Home() {
const allPosts = await api.post.getAll.query();
return (
<main className="text-white">
<ul className="flex flex-col gap-2 divide-y divide-slate-700">
{allPosts.map((post) => (
<PostView postWithAuthor={post} key={post.post.id} />
))}
</ul>
</main>
);
}
Krishna
Krishna8mo ago
I dont know how invalidate works... but Im pretty sure the assumption is flawed... you can use useQuery & call the refetch property within it on update of posts... better would be to use query-keys within usequery which would trigger a fresh refetch automatically
junior1
junior18mo ago
How to set trpc on next js 14, im lost in the documentsion Please help me