diff --git a/examples/react/start-basic-react-query/src/routes/index.tsx b/examples/react/start-basic-react-query/src/routes/index.tsx
index 37c8d237bd4..1d6eba35fae 100644
--- a/examples/react/start-basic-react-query/src/routes/index.tsx
+++ b/examples/react/start-basic-react-query/src/routes/index.tsx
@@ -1,12 +1,36 @@
+import * as React from 'react'
+import { useMutation } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
+import { createServerFn, useServerFn } from '@tanstack/react-start'
+
+const greetUser = createServerFn({ method: 'POST' })
+ .inputValidator((input: { name: string }) => input)
+ .handler(async ({ data }) => {
+ return Promise.resolve({ message: `Hello ${data.name}!` })
+ })
+
export const Route = createFileRoute('/')({
component: Home,
})
function Home() {
+ const greetingMutation = useMutation({
+ mutationFn: useServerFn(greetUser),
+ onSuccess: (data) => data,
+ })
+
return (
-
-
Welcome Home!!!
+
+
useServerFn + useMutation demo
+
+ {greetingMutation.data ? (
+
{greetingMutation.data.message}
+ ) : null}
)
}
diff --git a/packages/react-start/src/tests/useServerFnMutation.test-d.tsx b/packages/react-start/src/tests/useServerFnMutation.test-d.tsx
new file mode 100644
index 00000000000..10c307b22f2
--- /dev/null
+++ b/packages/react-start/src/tests/useServerFnMutation.test-d.tsx
@@ -0,0 +1,36 @@
+import { useMutation } from '@tanstack/react-query'
+import { createServerFn, useServerFn } from '../index'
+
+const serverFn = createServerFn({ method: 'POST' })
+ .inputValidator((input: { name: string }) => input)
+ .handler(async ({ data }) => ({
+ message: `Hello ${data.name}!`,
+ }))
+
+const optionalServerFn = createServerFn().handler(async () => ({
+ ok: true as const,
+}))
+
+export function UseServerFnMutationRegressionComponent() {
+ const mutation = useMutation({
+ mutationFn: useServerFn(serverFn),
+ onSuccess: (data, variables) => {
+ data.message
+ variables.data.name
+ },
+ })
+
+ void mutation
+ return null
+}
+
+export function useOptionalServerFnRegressionHook() {
+ const optionalHandler = useServerFn(optionalServerFn)
+
+ optionalHandler().then((result) => {
+ result.ok
+ })
+
+ void optionalHandler()
+ void optionalHandler(undefined)
+}
diff --git a/packages/react-start/src/useServerFn.ts b/packages/react-start/src/useServerFn.ts
index 67a09a4358c..57e4a1a4278 100644
--- a/packages/react-start/src/useServerFn.ts
+++ b/packages/react-start/src/useServerFn.ts
@@ -1,13 +1,30 @@
import * as React from 'react'
import { isRedirect, useRouter } from '@tanstack/react-router'
+type AwaitedReturn
) => Promise> = Awaited<
+ ReturnType
+>
+
+type NonUndefined = Exclude
+
+type UseServerFnReturn) => Promise> =
+ Parameters extends []
+ ? () => Promise>
+ : Parameters extends [infer TVariables]
+ ? undefined extends TVariables
+ ? [NonUndefined] extends [never]
+ ? () => Promise>
+ : (variables?: NonUndefined) => Promise>
+ : (variables: TVariables) => Promise>
+ : (...args: Parameters) => Promise>
+
export function useServerFn) => Promise>(
serverFn: T,
-): (...args: Parameters) => ReturnType {
+): UseServerFnReturn {
const router = useRouter()
- return React.useCallback(
- async (...args: Array) => {
+ const handler = React.useCallback(
+ async (...args: Parameters) => {
try {
const res = await serverFn(...args)
@@ -15,16 +32,20 @@ export function useServerFn) => Promise>(
throw res
}
- return res
+ return res as AwaitedReturn
} catch (err) {
if (isRedirect(err)) {
err.options._fromLocation = router.state.location
- return router.navigate(router.resolveRedirect(err).options)
+ return router.navigate(
+ router.resolveRedirect(err).options,
+ ) as AwaitedReturn
}
throw err
}
},
[router, serverFn],
- ) as any
+ )
+
+ return handler as UseServerFnReturn
}
diff --git a/packages/solid-start/src/tests/useServerFnMutation.test-d.tsx b/packages/solid-start/src/tests/useServerFnMutation.test-d.tsx
new file mode 100644
index 00000000000..e0558eae176
--- /dev/null
+++ b/packages/solid-start/src/tests/useServerFnMutation.test-d.tsx
@@ -0,0 +1,33 @@
+import { createServerFn, useServerFn } from '../index'
+
+const serverFn = createServerFn({ method: 'POST' })
+ .inputValidator((input: { name: string }) => input)
+ .handler(async ({ data }) => ({
+ message: `Hello ${data.name}!`,
+ }))
+
+const optionalServerFn = createServerFn().handler(async () => ({
+ ok: true as const,
+}))
+
+export function UseServerFnRegressionComponent() {
+ const handler = useServerFn(serverFn)
+
+ handler({ data: { name: 'TanStack' } }).then((result) => {
+ result.message
+ })
+
+ void handler
+ return null
+}
+
+export function useOptionalServerFnRegressionHook() {
+ const handler = useServerFn(optionalServerFn)
+
+ handler().then((result) => {
+ result.ok
+ })
+
+ void handler()
+ void handler(undefined)
+}
diff --git a/packages/solid-start/src/useServerFn.ts b/packages/solid-start/src/useServerFn.ts
index b0949121b40..5a51da571ef 100644
--- a/packages/solid-start/src/useServerFn.ts
+++ b/packages/solid-start/src/useServerFn.ts
@@ -1,11 +1,28 @@
import { isRedirect, useRouter } from '@tanstack/solid-router'
+type AwaitedReturn) => Promise> = Awaited<
+ ReturnType
+>
+
+type NonUndefined = Exclude
+
+type UseServerFnReturn) => Promise> =
+ Parameters extends []
+ ? () => Promise>
+ : Parameters extends [infer TVariables]
+ ? undefined extends TVariables
+ ? [NonUndefined] extends [never]
+ ? () => Promise>
+ : (variables?: NonUndefined) => Promise>
+ : (variables: TVariables) => Promise>
+ : (...args: Parameters) => Promise>
+
export function useServerFn) => Promise>(
serverFn: T,
-): (...args: Parameters) => ReturnType {
+): UseServerFnReturn {
const router = useRouter()
- return (async (...args: Array) => {
+ const handler = async (...args: Parameters) => {
try {
const res = await serverFn(...args)
@@ -13,14 +30,18 @@ export function useServerFn) => Promise>(
throw res
}
- return res
+ return res as AwaitedReturn
} catch (err) {
if (isRedirect(err)) {
err.options._fromLocation = router.state.location
- return router.navigate(router.resolveRedirect(err).options)
+ return router.navigate(
+ router.resolveRedirect(err).options,
+ ) as AwaitedReturn
}
throw err
}
- }) as any
+ }
+
+ return handler as UseServerFnReturn
}