H
Hono3w ago
Tom

RPC + React Query Mutations

Hi all, When I create a react-query mutation using the hono client, if i return the json in a 200 response, the onSuccess does not populate with the right typings, it only shows overlapping keys from other response codes like 400 const $post = api.auth.signin.request.$post; const signInRequestMutation = useMutation< InferResponseType<typeof $post>, Error, InferRequestType<typeof $post>["json"] >({ mutationFn: async (data) => { const res = await $post({ json: data }); if (!res.ok) { throw new Error("An error occurred"); } // Should return: { success: true, user: { //User data } } const response = await res.json(); return response; }, onSuccess: (data) => { // Only data.success is typed as its also in my 400 res console.log(data.user) }, onError: (error) => { console.log(error); }, });
14 Replies
NaughtyShiba
NaughtyShiba3w ago
How does data typing look like? How does route type look like?
ambergristle
ambergristle3w ago
hi @Tom! i believe that the behavior you're describing is expected if i understand correctly, you've got an endpoint that could return a 200 response, or some 400+ responses (using return.json({}, 400)) consequently, the inferred return type is a union of all possible responses, regardless of status code a 'quick and dirty' solution is to throw to onError and return 400+ responses from there. this will omit them from the endpoint response typing
Tom
TomOP3w ago
yes this is what it looks like @ambergristle @NaughtyShiba :
const app = new Hono()
.post(
"/signin",
zValidator("json", signInRequestSchema, (result, c) => {
if (!result.success) {
return c.json(
{
success: false,
errors: errorBag.generateErrors(result.error.errors),
},
400,
);
}
}),
async (c) => {
const existingUser = await db.query.usersTable.findFirst({
where: eq(usersTable.email, c.req.valid("json").email),
});

if (!existingUser) {
return c.json(
{
success:false,
errors: [
{
path: "email",
message: "Email address does not exist",
},
],
},
400,
);
}

// More work

return c.json({
success: true,
data: {
user: {}
},
});
},
)
const app = new Hono()
.post(
"/signin",
zValidator("json", signInRequestSchema, (result, c) => {
if (!result.success) {
return c.json(
{
success: false,
errors: errorBag.generateErrors(result.error.errors),
},
400,
);
}
}),
async (c) => {
const existingUser = await db.query.usersTable.findFirst({
where: eq(usersTable.email, c.req.valid("json").email),
});

if (!existingUser) {
return c.json(
{
success:false,
errors: [
{
path: "email",
message: "Email address does not exist",
},
],
},
400,
);
}

// More work

return c.json({
success: true,
data: {
user: {}
},
});
},
)
ambergristle
ambergristle3w ago
@Tom can you please enclose code snippets in triple-backticks? thanks! you can also specify the language at the top of the codeblock, immediately after the opening backticks ```typescript
const example = (val: string) => console.log(val)
const example = (val: string) => console.log(val)
noice!
Tom
TomOP3w ago
eventually haha
ambergristle
ambergristle3w ago
so the ez play is just to throw you could also try creating your own InferResponseType type implementation but that's not looking so simple
Tom
TomOP3w ago
so if i throw:
mutationFn: async (data) => {
const res = await $post({ json: data });

if (!res.ok) throw new Error(res.statusText);

return res.json();
},
onSuccess: (data) => {
console.log(data);
},
onError: (error) => {
return console.log(error);
},
mutationFn: async (data) => {
const res = await $post({ json: data });

if (!res.ok) throw new Error(res.statusText);

return res.json();
},
onSuccess: (data) => {
console.log(data);
},
onError: (error) => {
return console.log(error);
},
i still have the same issue with the inclusion of a onError
ambergristle
ambergristle3w ago
sorry, i mean on the backend
Tom
TomOP3w ago
im just surprised nobody else has had this issue can you throw with returning the 400 res?
ambergristle
ambergristle3w ago
const app = new Hono()
.get('/', async (c) => {
let userIsAuthed = true
if (userIsAuthed) {
throw new HTTPException(401, {
message: 'Unauthorized'
})
}

return c.json({
test: 'hi'
})
})
.onError((error, c) => {
console.error(error)
if (error instanceof HTTPException) {
return c.json({
message: error.message
}, error.status)
}
return c.json({
message: 'something went wrong'
}, 500)
})
const app = new Hono()
.get('/', async (c) => {
let userIsAuthed = true
if (userIsAuthed) {
throw new HTTPException(401, {
message: 'Unauthorized'
})
}

return c.json({
test: 'hi'
})
})
.onError((error, c) => {
console.error(error)
if (error instanceof HTTPException) {
return c.json({
message: error.message
}, error.status)
}
return c.json({
message: 'something went wrong'
}, 500)
})
https://hono.dev/docs/api/exception you don't need to use the hono HTTPException class. i've ultimately set up my own error constructer. but it's a nice starting point because it bakes in the status code you should have a catch-all onError handler regardless, but i like to use mine as a centralized error-response handler
Tom
TomOP3w ago
ok so i found in backend, if i do this without the return before it:
c.json(
{
status: "error",
errors: [
{
path: "email",
message: "Email address does not exist",
},
],
},
400,
);
c.json(
{
status: "error",
errors: [
{
path: "email",
message: "Email address does not exist",
},
],
},
400,
);
our data in onSuccess is correct, but ofc we need to return c.json as we need to exit out early from the endpoint but yes this works as expected now, thank you for helping! still amazed how little this sort of thing is documented especially in the RPC section of the docs
ambergristle
ambergristle3w ago
hono is a newer project with a small dev team, so there are def edge cases + nuances that aren't covered fully (or at all) in the docs
ambergristle
ambergristle3w ago
but in this case, they actually do cover this: https://hono.dev/docs/guides/rpc#status-code
RPC - Hono
Web framework built on Web Standards for Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Node.js, and others. Fast, but not only fast.
ambergristle
ambergristle3w ago
looks like InferResponseType takes an optional second arg that's a status code (or union of codes) that you want to include in the type result TIL pretty sick that you have so much flexibility in terms of client error handling

Did you find this page helpful?