Augustin Sorel
Augustin Sorelā€¢6mo ago

Beginner: form field error handling ?

hey guys, I have a question regarding error handling with trpc. How could I specify a form field in the error that I am returning from the backend ? The doc page does not explain this. The way I do it is by checking for a specific code. But surely this wouldn't scale very well for a large form ? What would be a good way to handle error then ? So for example let's say I got an auth router like so:
export const insertUser = async (user: InsertUser) => {
try {
return await db.insert(users).values(user).returning();
} catch (e) {
// any way to avoid this casting btw ? Using instanceof doesnt work with PostgresError
const error = e as PostgresError;

if (error?.constraint_name === "user_email_unique") {
throw new TRPCError({
code: "CONFLICT",
message: "email must be unique",
});
}

throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "something went wrong",
});
}
};

export const authRouter = createTRPCRouter({
signUp: publicProcedure.input(signUpSchema).mutation(async ({ input }) => {
const hashedPassword = await new Scrypt().hash(input.password);
const userId = generateId(15);

await insertUser({ id: userId, email: input.email, hashedPassword });

const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);

cookies().set(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes,
);
}),
});
export const insertUser = async (user: InsertUser) => {
try {
return await db.insert(users).values(user).returning();
} catch (e) {
// any way to avoid this casting btw ? Using instanceof doesnt work with PostgresError
const error = e as PostgresError;

if (error?.constraint_name === "user_email_unique") {
throw new TRPCError({
code: "CONFLICT",
message: "email must be unique",
});
}

throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "something went wrong",
});
}
};

export const authRouter = createTRPCRouter({
signUp: publicProcedure.input(signUpSchema).mutation(async ({ input }) => {
const hashedPassword = await new Scrypt().hash(input.password);
const userId = generateId(15);

await insertUser({ id: userId, email: input.email, hashedPassword });

const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);

cookies().set(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes,
);
}),
});
here is how I call it in the client:
const signUp = api.auth.signUp.useMutation({
onSuccess: () => {
router.push("/");
},
onError: (error) => {
// not a big fan of this
if (error.data?.code === "CONFLICT") {
return form.setError("email", { message: error.message });
}

return form.setError("root", { message: error.message });
},
});
const signUp = api.auth.signUp.useMutation({
onSuccess: () => {
router.push("/");
},
onError: (error) => {
// not a big fan of this
if (error.data?.code === "CONFLICT") {
return form.setError("email", { message: error.message });
}

return form.setError("root", { message: error.message });
},
});
3 Replies
Liltripple_reid
Liltripple_reidā€¢5mo ago
you can check this video from jack herrington although not for trpc itself but the concept is close enough to the form validation you want to achieve https://youtu.be/VLk45JBe8L8?si=ioRDyMJ-q9XUs_Wj
Jack Herrington
YouTube
React Hook Form & React 19 Form Actions, The Right Way
Let's figure out how to get form actions and React Hook Form to work together to get the best from both; client AND server side validation when JavaScript is enabled on the client, and user friendly server side validation when it's not enabled. šŸŽ‰ Free Forms Tutorial Series: https://www.pronextjs.dev/tutorials/forms-management-with-next-js-app-r...
Augustin Sorel
Augustin Sorelā€¢5mo ago
thanks for linking this video, however it does not explain how to do form field error handling when the backend is returning an error.
Liltripple_reid
Liltripple_reidā€¢5mo ago
@Augustin Sorel I think what you're looking for is from minute 11:30 gotta do some zod stuff