T
TanStack2mo ago
continuing-cyan

Throwing error inside Server Function's handler crashes the app

Not sure if I messed up something, but my Tanstack Start app will crash if an error is thrown inside the server function's handler, Inspired by the doc's example here
export const getRecipeByIdFn = createServerFn().handler(async () => {
const user = false

if (!user) {
throw redirect({ to: "/sign-in" })
}
return {
id: "12345",
name: "Test Recipe",
description: "This is a test recipe",
}
})
export const getRecipeByIdFn = createServerFn().handler(async () => {
const user = false

if (!user) {
throw redirect({ to: "/sign-in" })
}
return {
id: "12345",
name: "Test Recipe",
description: "This is a test recipe",
}
})
My app will crash with the following error
node:internal/process/promises:392
new UnhandledPromiseRejection(reason);
^

UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<_Response>".
at throwUnhandledRejectionsMode (node:internal/process/promises:392:7)
at processPromiseRejections (node:internal/process/promises:475:17)
at process.processTicksAndRejections (node:internal/process/task_queues:106:32) {
code: 'ERR_UNHANDLED_REJECTION'
}
node:internal/process/promises:392
new UnhandledPromiseRejection(reason);
^

UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<_Response>".
at throwUnhandledRejectionsMode (node:internal/process/promises:392:7)
at processPromiseRejections (node:internal/process/promises:475:17)
at process.processTicksAndRejections (node:internal/process/task_queues:106:32) {
code: 'ERR_UNHANDLED_REJECTION'
}
The function is called from a query hook
export const recipeQueryKey = "recipe"

export const recipeQueryOptions = (id: string) =>
queryOptions({
queryKey: [recipeQueryKey, { id }],
queryFn: () => getRecipeByIdFn(),
})

export const useRecipe = (id: string) => {
return useSuspenseQuery(recipeQueryOptions(id))
}
export const recipeQueryKey = "recipe"

export const recipeQueryOptions = (id: string) =>
queryOptions({
queryKey: [recipeQueryKey, { id }],
queryFn: () => getRecipeByIdFn(),
})

export const useRecipe = (id: string) => {
return useSuspenseQuery(recipeQueryOptions(id))
}
and the query is called in a loader
export const Route = createFileRoute("/app/recipes/$id")({
loader: async ({ params, context }) => {
context.queryClient.ensureQueryData(recipeQueryOptions(params.id))
},
component: RouteComponent,
})
export const Route = createFileRoute("/app/recipes/$id")({
loader: async ({ params, context }) => {
context.queryClient.ensureQueryData(recipeQueryOptions(params.id))
},
component: RouteComponent,
})
Is this expected? I think it works before
Server Functions | TanStack Start React Docs
What are Server Functions? Server functions let you define server-only logic that can be called from anywhere in your application loaders, components, hooks, or other server functions. They run on the...
4 Replies
constant-blue
constant-blue2mo ago
Call the serverFn with this wrapper: queryFn: () => useServerFn(getRecipeByIdFn)()
continuing-cyan
continuing-cyanOP2mo ago
Thanks. This works, but it makes exporting and reusing the queryOptions more annoying tho
// I have to pass serverFn everytime I use recipeQueryOptions
export const recipeQueryOptions = (id: string, serverFn: any) =>
queryOptions({
queryKey: [recipeQueryKey, { id }],
queryFn: () => serverFn(),
})

export const useRecipe = (id: string) => {
const serverFn = useServerFn(getRecipeByIdFn)
return useSuspenseQuery(recipeQueryOptions(id, serverFn))
}
// I have to pass serverFn everytime I use recipeQueryOptions
export const recipeQueryOptions = (id: string, serverFn: any) =>
queryOptions({
queryKey: [recipeQueryKey, { id }],
queryFn: () => serverFn(),
})

export const useRecipe = (id: string) => {
const serverFn = useServerFn(getRecipeByIdFn)
return useSuspenseQuery(recipeQueryOptions(id, serverFn))
}
not sure if there's a better pattern? and I will not be able to use the queryOptions in the loader since I need to call useServer hook to pass to the query options
loader: async ({ params, context }) => {
// dont think this is allowed?
const serverFn = useServerFn(getRecipeByIdFn)
context.queryClient.ensureQueryData(recipeQueryOptions(params.id, serverFn))
}
loader: async ({ params, context }) => {
// dont think this is allowed?
const serverFn = useServerFn(getRecipeByIdFn)
context.queryClient.ensureQueryData(recipeQueryOptions(params.id, serverFn))
}
eager-peach
eager-peach2mo ago
are you using the query integration?
eager-peach
eager-peach2mo ago
TanStack Query Integration | TanStack Router Docs
[!IMPORTANT] This integration automates SSR dehydration/hydration and streaming between TanStack Router and TanStack Query. If you haven't read the standard guide, start there. What you get Automatic...

Did you find this page helpful?