diff --git a/docs/config.json b/docs/config.json
index 6b289ada6..b62b94a5d 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -156,8 +156,16 @@
"to": "framework/react/guides/react-native"
},
{
- "label": "SSR/TanStack Start/Next.js",
- "to": "framework/react/guides/ssr"
+ "label": "TanStack Start",
+ "to": "framework/react/guides/tanstack-start"
+ },
+ {
+ "label": "Next.js",
+ "to": "framework/react/guides/nextjs"
+ },
+ {
+ "label": "Remix",
+ "to": "framework/react/guides/remix"
},
{
"label": "Debugging",
@@ -418,10 +426,6 @@
"label": "Functions / useForm",
"to": "framework/react/reference/functions/useForm"
},
- {
- "label": "Functions / useTransform",
- "to": "framework/react/reference/functions/useTransform"
- },
{
"label": "Types / FieldComponent",
"to": "framework/react/reference/type-aliases/FieldComponent"
diff --git a/docs/framework/angular/guides/submission-handling.md b/docs/framework/angular/guides/submission-handling.md
index e5fae2477..2623766ab 100644
--- a/docs/framework/angular/guides/submission-handling.md
+++ b/docs/framework/angular/guides/submission-handling.md
@@ -102,3 +102,22 @@ export class AppComponent {
})
}
```
+
+> In a situation where you want to be able to submit in an invalid state `canSubmitWhenInvalid` boolean flag can be provided to useForm.
+
+```angular-ts
+import { injectForm } from '@tanstack/angular-form';
+
+export class AppComponent {
+ form = injectForm({
+ defaultValues: {
+ data: '',
+ },
+ canSubmitWhenInvalid: true,
+ onSubmit: async ({ value }) => {
+ // Do something with the values
+ console.log(value)
+ },
+ });
+}
+```
diff --git a/docs/framework/react/guides/arrays.md b/docs/framework/react/guides/arrays.md
index 90a5e49f4..5ffe40a3e 100644
--- a/docs/framework/react/guides/arrays.md
+++ b/docs/framework/react/guides/arrays.md
@@ -52,6 +52,10 @@ Finally, you can use a subfield like so:
```
+## Why Index as Key?
+
+You may notice that these examples use `key={i}` the array index as the key prop. React's documentation generally advises _against_ using array indices as keys when items can be reordered or deleted. TanStack Form is an exception to this rule. Because field names in TanStack Form arrays are index-based, using the array index as `key` is required, it keeps React component instances, form store state, and field names in sync.
+
## Full Example
```jsx
diff --git a/docs/framework/react/guides/nextjs.md b/docs/framework/react/guides/nextjs.md
new file mode 100644
index 000000000..7215d1e2e
--- /dev/null
+++ b/docs/framework/react/guides/nextjs.md
@@ -0,0 +1,162 @@
+---
+id: nextjs
+title: TanStack Form - NextJs
+---
+
+## Using TanStack Form in a Next.js App Router
+
+> Before reading this section, it's suggested you understand how React Server Components and React Server Actions work. [Check out this blog series for more information](https://playfulprogramming.com/collections/react-beyond-the-render)
+
+This section focuses on integrating TanStack Form with `Next.js`, particularly using the `App Router` and `Server Actions`.
+
+### Next.js Prerequisites
+
+- Start a new `Next.js` project, following the steps in the [Next.js Documentation](https://nextjs.org/docs/getting-started/installation).
+- Install `@tanstack/react-form-nextjs`
+- Install any [form validator](./validation#validation-through-schema-libraries) of your choice. [Optional]
+
+## App Router integration
+
+Let's start by creating a `formOption` that we'll use to share the form's shape across the client and server.
+
+```ts shared-code.ts
+import { formOptions } from '@tanstack/react-form-nextjs'
+
+// You can pass other form options here
+export const formOpts = formOptions({
+ defaultValues: {
+ firstName: '',
+ age: 0,
+ },
+})
+```
+
+Next, we can create [a React Server Action](https://playfulprogramming.com/posts/what-are-react-server-components) that will handle the form submission on the server.
+
+```ts action.ts
+'use server'
+
+import {
+ ServerValidateError,
+ createServerValidate,
+} from '@tanstack/react-form-nextjs'
+
+import { formOpts } from './shared-code'
+
+// Create the server action that will infer the types of the form from `formOpts`
+const serverValidate = createServerValidate({
+ ...formOpts,
+ onServerValidate: ({ value }) => {
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up'
+ }
+ },
+})
+
+export default async function someAction(prev: unknown, formData: FormData) {
+ try {
+ const validatedData = await serverValidate(formData)
+ console.log('validatedData', validatedData)
+ // Persist the form data to the database
+ // await sql`
+ // INSERT INTO users (name, email, password)
+ // VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
+ // `
+ } catch (e) {
+ if (e instanceof ServerValidateError) {
+ return e.formState
+ }
+
+ // Some other error occurred while validating your form
+ throw e
+ }
+
+ // Your form has successfully validated!
+}
+```
+
+Finally, we'll use `someAction` in our client-side form component.
+
+```tsx client-component.tsx
+'use client'
+
+import { useActionState } from 'react'
+import {
+ initialFormState,
+ mergeForm,
+ useForm,
+ useStore,
+ useTransform,
+} from '@tanstack/react-form-nextjs'
+
+import someAction from './action'
+import { formOpts } from './shared-code'
+
+export const ClientComp = () => {
+ const [state, action] = useActionState(someAction, initialFormState)
+
+ const form = useForm({
+ ...formOpts,
+ transform: useTransform((baseForm) => mergeForm(baseForm, state!), [state]),
+ })
+
+ const formErrors = useStore(form.store, (formState) => formState.errors)
+
+ return (
+
+ value < 8 ? 'Client validation: You must be at least 8' : undefined,
+ }}
+ >
+ {(field) => {
+ return (
+
+ )
+ }}
+
+ [formState.canSubmit, formState.isSubmitting]}
+ >
+ {([canSubmit, isSubmitting]) => (
+
+ )}
+
+
+ )
+}
+```
+
+### useTransform
+
+you may have noticed util function `useTransform` being used throughout these examples, it's primary responsibility is the merging of the server and client state. Under the hood it is a useCallback whose deps are that of the server state, when the server state changes it will automatically patch the client state.
+
+## debugging
+
+> If you get the following error in your Next.js application:
+>
+> ```typescript
+> x You're importing a component that needs `useState`. This React hook only works in a client component. To fix, mark the file (or its parent) with the `"use client"` directive.
+> ```
+>
+> This is because you're not importing server-side code from `@tanstack/react-form-nextjs`. Ensure you're importing the correct module based on the environment.
+>
+> [This is a limitation of Next.js](https://github.com/phryneas/rehackt). Other meta-frameworks will likely not have this same problem.
diff --git a/docs/framework/react/guides/remix.md b/docs/framework/react/guides/remix.md
new file mode 100644
index 000000000..1f3b75989
--- /dev/null
+++ b/docs/framework/react/guides/remix.md
@@ -0,0 +1,158 @@
+---
+id: remix
+title: TanStack Form - Remix
+---
+
+## Using TanStack Form in Remix
+
+> Before reading this section, it's suggested you understand how Remix actions work. [Check out Remix's docs for more information](https://remix.run/docs/en/main/discussion/data-flow#route-action)
+
+### Remix Prerequisites
+
+- Start a new `Remix` project, following the steps in the [Remix Documentation](https://remix.run/docs/en/main/start/quickstart).
+- Install `@tanstack/react-form-start`
+- Install any [form validator](./validation#validation-through-schema-libraries) of your choice. [Optional]
+
+## Remix integration
+
+Let's start by creating a `formOption` that we'll use to share the form's shape across the client and server.
+
+```tsx routes/_index/route.tsx
+import { formOptions } from '@tanstack/react-form-remix'
+
+// You can pass other form options here
+export const formOpts = formOptions({
+ defaultValues: {
+ firstName: '',
+ age: 0,
+ },
+})
+```
+
+Next, we can create [an action](https://remix.run/docs/en/main/discussion/data-flow#route-action) that will handle the form submission on the server.
+
+```tsx routes/_index/route.tsx
+import {
+ ServerValidateError,
+ createServerValidate,
+ formOptions,
+} from '@tanstack/react-form-remix'
+
+import type { ActionFunctionArgs } from '@remix-run/node'
+
+// Create the server action that will infer the types of the form from `formOpts`
+const serverValidate = createServerValidate({
+ ...formOpts,
+ onServerValidate: ({ value }) => {
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up'
+ }
+ },
+})
+
+export async function action({ request }: ActionFunctionArgs) {
+ const formData = await request.formData()
+ try {
+ const validatedData = await serverValidate(formData)
+ console.log('validatedData', validatedData)
+ // Persist the form data to the database
+ // await sql`
+ // INSERT INTO users (name, email, password)
+ // VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
+ // `
+ } catch (e) {
+ if (e instanceof ServerValidateError) {
+ return e.formState
+ }
+
+ // Some other error occurred while validating your form
+ throw e
+ }
+
+ // Your form has successfully validated!
+}
+```
+
+Finally, the `action` will be called when the form submits.
+
+```tsx
+// routes/_index/route.tsx
+import { Form, useActionData } from '@remix-run/react'
+import { mergeForm, useForm, useStore } from '@tanstack/react-form'
+import {
+ ServerValidateError,
+ createServerValidate,
+ formOptions,
+ initialFormState,
+ useTransform,
+} from '@tanstack/react-form-remix'
+
+export default function Index() {
+ const actionData = useActionData()
+
+ const form = useForm({
+ ...formOpts,
+ transform: useTransform(
+ (baseForm) => mergeForm(baseForm, actionData ?? initialFormState),
+ [actionData],
+ ),
+ })
+
+ const formErrors = useStore(form.store, (formState) => formState.errors)
+
+ return (
+
+ value < 8 ? 'Client validation: You must be at least 8' : undefined,
+ }}
+ >
+ {(field) => {
+ return (
+
+ )
+ }}
+
+ [formState.canSubmit, formState.isSubmitting]}
+ >
+ {([canSubmit, isSubmitting]) => (
+
+ )}
+
+
+ )
+}
+```
+
+### useTransform
+
+you may have noticed util function `useTransform` being used throughout these examples, its primary responsibility is the merging of the server and client state. Under the hood it is a useCallback whose deps are that of the server state, when the server state changes it will automatically patch the client state.
+
+```tsx
+const form = useForm({
+ ...formOpts,
+ transform: useTransform(
+ (baseForm) => mergeForm(baseForm, actionData ?? initialFormState),
+ [actionData],
+ ),
+})
+```
diff --git a/docs/framework/react/guides/ssr.md b/docs/framework/react/guides/ssr.md
deleted file mode 100644
index 47c45ffb6..000000000
--- a/docs/framework/react/guides/ssr.md
+++ /dev/null
@@ -1,491 +0,0 @@
----
-id: ssr
-title: React Meta-Framework Usage
----
-
-TanStack Form is compatible with React out of the box, supporting `SSR` and being framework-agnostic. However, specific configurations are necessary, according to your chosen framework.
-
-Today we support the following meta-frameworks:
-
-- [TanStack Start](https://tanstack.com/start/)
-- [Next.js](https://nextjs.org/)
-- [Remix](https://remix.run)
-
-## Using TanStack Form in TanStack Start
-
-This section focuses on integrating TanStack Form with TanStack Start.
-
-### TanStack Start Prerequisites
-
-- Start a new `TanStack Start` project, following the steps in the [TanStack Start Quickstart Guide](https://tanstack.com/router/latest/docs/framework/react/guide/tanstack-start)
-- Install `@tanstack/react-form`
-
-### Start integration
-
-Let's start by creating a `formOption` that we'll use to share the form's shape across the client and server.
-
-```typescript
-// app/routes/index.tsx, but can be extracted to any other path
-import { formOptions } from '@tanstack/react-form-start'
-
-// You can pass other form options here
-export const formOpts = formOptions({
- defaultValues: {
- firstName: '',
- age: 0,
- },
-})
-```
-
-Next, we can create [a Start Server Function](https://tanstack.com/start/latest/docs/framework/react/server-functions) that will handle the form submission on the server.
-
-```typescript
-// app/routes/index.tsx, but can be extracted to any other path
-import {
- createServerValidate,
- ServerValidateError,
-} from '@tanstack/react-form-start'
-
-const serverValidate = createServerValidate({
- ...formOpts,
- onServerValidate: ({ value }) => {
- if (value.age < 12) {
- return 'Server validation: You must be at least 12 to sign up'
- }
- },
-})
-
-export const handleForm = createServerFn({
- method: 'POST',
-})
- .inputValidator((data: unknown) => {
- if (!(data instanceof FormData)) {
- throw new Error('Invalid form data')
- }
- return data
- })
- .handler(async (ctx) => {
- try {
- const validatedData = await serverValidate(ctx.data)
- console.log('validatedData', validatedData)
- // Persist the form data to the database
- // await sql`
- // INSERT INTO users (name, email, password)
- // VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
- // `
- } catch (e) {
- if (e instanceof ServerValidateError) {
- // Log form errors or do any other logic here
- return e.response
- }
-
- // Some other error occurred when parsing the form
- console.error(e)
- setResponseStatus(500)
- return 'There was an internal error'
- }
-
- return 'Form has validated successfully'
- })
-```
-
-Then we need to establish a way to grab the form data from `serverValidate`'s `response` using another server action:
-
-```typescript
-// app/routes/index.tsx, but can be extracted to any other path
-import { getFormData } from '@tanstack/react-form-start'
-
-export const getFormDataFromServer = createServerFn({ method: 'GET' }).handler(
- async () => {
- return getFormData()
- },
-)
-```
-
-Finally, we'll use `getFormDataFromServer` in our loader to get the state from our server into our client and `handleForm` in our client-side form component.
-
-```tsx
-// app/routes/index.tsx
-import { createFileRoute } from '@tanstack/react-router'
-import {
- mergeForm,
- useForm,
- useStore,
- useTransform,
-} from '@tanstack/react-form-start'
-
-export const Route = createFileRoute('/')({
- component: Home,
- loader: async () => ({
- state: await getFormDataFromServer(),
- }),
-})
-
-function Home() {
- const { state } = Route.useLoaderData()
- const form = useForm({
- ...formOpts,
- transform: useTransform((baseForm) => mergeForm(baseForm, state), [state]),
- })
-
- const formErrors = useStore(form.store, (formState) => formState.errors)
-
- return (
-
- value < 8 ? 'Client validation: You must be at least 8' : undefined,
- }}
- >
- {(field) => {
- return (
-
- )
- }}
-
- [formState.canSubmit, formState.isSubmitting]}
- >
- {([canSubmit, isSubmitting]) => (
-
- )}
-
-
- )
-}
-```
-
-## Using TanStack Form in a Next.js App Router
-
-> Before reading this section, it's suggested you understand how React Server Components and React Server Actions work. [Check out this blog series for more information](https://playfulprogramming.com/collections/react-beyond-the-render)
-
-This section focuses on integrating TanStack Form with `Next.js`, particularly using the `App Router` and `Server Actions`.
-
-### Next.js Prerequisites
-
-- Start a new `Next.js` project, following the steps in the [Next.js Documentation](https://nextjs.org/docs/getting-started/installation). Ensure you select `yes` for `Would you like to use App Router?` during the setup to access all new features provided by Next.js.
-- Install `@tanstack/react-form`
-- Install any [form validator](./validation#validation-through-schema-libraries) of your choice. [Optional]
-
-## App Router integration
-
-Let's start by creating a `formOption` that we'll use to share the form's shape across the client and server.
-
-```typescript
-// shared-code.ts
-// Notice the import path is different from the client
-import { formOptions } from '@tanstack/react-form-nextjs'
-
-// You can pass other form options here
-export const formOpts = formOptions({
- defaultValues: {
- firstName: '',
- age: 0,
- },
-})
-```
-
-Next, we can create [a React Server Action](https://playfulprogramming.com/posts/what-are-react-server-components) that will handle the form submission on the server.
-
-```typescript
-// action.ts
-'use server'
-
-// Notice the import path is different from the client
-import {
- ServerValidateError,
- createServerValidate,
-} from '@tanstack/react-form-nextjs'
-import { formOpts } from './shared-code'
-
-// Create the server action that will infer the types of the form from `formOpts`
-const serverValidate = createServerValidate({
- ...formOpts,
- onServerValidate: ({ value }) => {
- if (value.age < 12) {
- return 'Server validation: You must be at least 12 to sign up'
- }
- },
-})
-
-export default async function someAction(prev: unknown, formData: FormData) {
- try {
- const validatedData = await serverValidate(formData)
- console.log('validatedData', validatedData)
- // Persist the form data to the database
- // await sql`
- // INSERT INTO users (name, email, password)
- // VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
- // `
- } catch (e) {
- if (e instanceof ServerValidateError) {
- return e.formState
- }
-
- // Some other error occurred while validating your form
- throw e
- }
-
- // Your form has successfully validated!
-}
-```
-
-Finally, we'll use `someAction` in our client-side form component.
-
-```tsx
-// client-component.tsx
-'use client'
-
-import { useActionState } from 'react'
-import {
- initialFormState,
- mergeForm,
- useForm,
- useStore,
- useTransform,
-} from '@tanstack/react-form-nextjs'
-import someAction from './action'
-import { formOpts } from './shared-code'
-
-export const ClientComp = () => {
- const [state, action] = useActionState(someAction, initialFormState)
-
- const form = useForm({
- ...formOpts,
- transform: useTransform((baseForm) => mergeForm(baseForm, state!), [state]),
- })
-
- const formErrors = useStore(form.store, (formState) => formState.errors)
-
- return (
-
- value < 8 ? 'Client validation: You must be at least 8' : undefined,
- }}
- >
- {(field) => {
- return (
-
- )
- }}
-
- [formState.canSubmit, formState.isSubmitting]}
- >
- {([canSubmit, isSubmitting]) => (
-
- )}
-
-
- )
-}
-```
-
-Here, we're using [React's `useActionState` hook](https://playfulprogramming.com/posts/what-is-use-action-state-and-form-status) and TanStack Form's `useTransform` hook to merge state returned from the server action with the form state.
-
-> If you get the following error in your Next.js application:
->
-> ```typescript
-> x You're importing a component that needs `useState`. This React hook only works in a client component. To fix, mark the file (or its parent) with the `"use client"` directive.
-> ```
->
-> This is because you're not importing server-side code from `@tanstack/react-form-nextjs`. Ensure you're importing the correct module based on the environment.
->
-> [This is a limitation of Next.js](https://github.com/phryneas/rehackt). Other meta-frameworks will likely not have this same problem.
-
-## Using TanStack Form in Remix
-
-> Before reading this section, it's suggested you understand how Remix actions work. [Check out Remix's docs for more information](https://remix.run/docs/en/main/discussion/data-flow#route-action)
-
-### Remix Prerequisites
-
-- Start a new `Remix` project, following the steps in the [Remix Documentation](https://remix.run/docs/en/main/start/quickstart).
-- Install `@tanstack/react-form`
-- Install any [form validator](./validation#validation-through-schema-libraries) of your choice. [Optional]
-
-## Remix integration
-
-Let's start by creating a `formOption` that we'll use to share the form's shape across the client and server.
-
-```typescript
-// routes/_index/route.tsx
-import { formOptions } from '@tanstack/react-form-remix'
-
-// You can pass other form options here
-export const formOpts = formOptions({
- defaultValues: {
- firstName: '',
- age: 0,
- },
-})
-```
-
-Next, we can create [an action](https://remix.run/docs/en/main/discussion/data-flow#route-action) that will handle the form submission on the server.
-
-```tsx
-// routes/_index/route.tsx
-
-import {
- ServerValidateError,
- createServerValidate,
- formOptions,
-} from '@tanstack/react-form-remix'
-
-import type { ActionFunctionArgs } from '@remix-run/node'
-
-// export const formOpts = formOptions({
-
-// Create the server action that will infer the types of the form from `formOpts`
-const serverValidate = createServerValidate({
- ...formOpts,
- onServerValidate: ({ value }) => {
- if (value.age < 12) {
- return 'Server validation: You must be at least 12 to sign up'
- }
- },
-})
-
-export async function action({ request }: ActionFunctionArgs) {
- const formData = await request.formData()
- try {
- const validatedData = await serverValidate(formData)
- console.log('validatedData', validatedData)
- // Persist the form data to the database
- // await sql`
- // INSERT INTO users (name, email, password)
- // VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
- // `
- } catch (e) {
- if (e instanceof ServerValidateError) {
- return e.formState
- }
-
- // Some other error occurred while validating your form
- throw e
- }
-
- // Your form has successfully validated!
-}
-```
-
-Finally, the `action` will be called when the form submits.
-
-```tsx
-// routes/_index/route.tsx
-import {
- Form,
- mergeForm,
- useActionData,
- useForm,
- useStore,
- useTransform,
-} from '@tanstack/react-form'
-import {
- ServerValidateError,
- createServerValidate,
- formOptions,
- initialFormState,
-} from '@tanstack/react-form-remix'
-
-import type { ActionFunctionArgs } from '@remix-run/node'
-
-// export const formOpts = formOptions({
-
-// const serverValidate = createServerValidate({
-
-// export async function action({request}: ActionFunctionArgs) {
-
-export default function Index() {
- const actionData = useActionData()
-
- const form = useForm({
- ...formOpts,
- transform: useTransform(
- (baseForm) => mergeForm(baseForm, actionData ?? initialFormState),
- [actionData],
- ),
- })
-
- const formErrors = useStore(form.store, (formState) => formState.errors)
-
- return (
-
- value < 8 ? 'Client validation: You must be at least 8' : undefined,
- }}
- >
- {(field) => {
- return (
-
- )
- }}
-
- [formState.canSubmit, formState.isSubmitting]}
- >
- {([canSubmit, isSubmitting]) => (
-
- )}
-
-
- )
-}
-```
-
-Here, we're using [Remix's `useActionData` hook](https://remix.run/docs/en/main/hooks/use-action-data) and TanStack Form's `useTransform` hook to merge state returned from the server action with the form state.
diff --git a/docs/framework/react/guides/submission-handling.md b/docs/framework/react/guides/submission-handling.md
index 7198217be..341ab5f35 100644
--- a/docs/framework/react/guides/submission-handling.md
+++ b/docs/framework/react/guides/submission-handling.md
@@ -91,3 +91,18 @@ const form = useForm({
},
})
```
+
+> In a situation where you want to be able to submit in an invalid state `canSubmitWhenInvalid` boolean flag can be provided to useForm.
+
+```tsx
+const form = useForm({
+ defaultValues: {
+ data: '',
+ },
+ canSubmitWhenInvalid: true,
+ onSubmit: async ({ value }) => {
+ // Do something with the values
+ console.log(value)
+ },
+})
+```
diff --git a/docs/framework/react/guides/tanstack-start.md b/docs/framework/react/guides/tanstack-start.md
new file mode 100644
index 000000000..e4f513f71
--- /dev/null
+++ b/docs/framework/react/guides/tanstack-start.md
@@ -0,0 +1,174 @@
+---
+id: tanstack-start
+title: TanStack Form - TanStack Start
+---
+
+## Using TanStack Form in TanStack Start
+
+> Before reading this section, it's suggested you understand how TanStack `serverFunctions`. [Check out TanStack docs for more information](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions)
+
+### TanStack Start Prerequisites
+
+- Start a new `TanStack Start` project, following the steps in the [TanStack Start Quickstart Guide](https://tanstack.com/router/latest/docs/framework/react/guide/tanstack-start)
+- Install `@tanstack/react-form`
+- Install any [form validator](./validation#validation-through-schema-libraries) of your choice. [Optional]
+
+### Start integration
+
+Let's start by creating a `formOption` that we'll use to share the form's shape across the client and server.
+
+```tsx app/routes/index.tsx
+import { formOptions } from '@tanstack/react-form-start'
+
+// You can pass other form options here
+export const formOpts = formOptions({
+ defaultValues: {
+ firstName: '',
+ age: 0,
+ },
+})
+```
+
+Next, we can create [a Start Server Function](https://tanstack.com/start/latest/docs/framework/react/server-functions) that will handle the form submission on the server.
+
+```tsx app/routes/index.tsx
+import {
+ createServerValidate,
+ ServerValidateError,
+} from '@tanstack/react-form-start'
+
+const serverValidate = createServerValidate({
+ ...formOpts,
+ onServerValidate: ({ value }) => {
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up'
+ }
+ },
+})
+
+export const handleForm = createServerFn({
+ method: 'POST',
+})
+ .inputValidator((data: unknown) => {
+ if (!(data instanceof FormData)) {
+ throw new Error('Invalid form data')
+ }
+ return data
+ })
+ .handler(async (ctx) => {
+ try {
+ const validatedData = await serverValidate(ctx.data)
+ console.log('validatedData', validatedData)
+ // Persist the form data to the database
+ // await sql`
+ // INSERT INTO users (name, email, password)
+ // VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
+ // `
+ } catch (e) {
+ if (e instanceof ServerValidateError) {
+ // Log form errors or do any other logic here
+ return e.response
+ }
+
+ // Some other error occurred when parsing the form
+ console.error(e)
+ setResponseStatus(500)
+ return 'There was an internal error'
+ }
+
+ return 'Form has validated successfully'
+ })
+```
+
+Then we need to establish a way to grab the form data from `serverValidate`'s `response` using another server action:
+
+```tsx app/routes/index.tsx
+import { getFormData } from '@tanstack/react-form-start'
+
+export const getFormDataFromServer = createServerFn({ method: 'GET' }).handler(
+ async () => {
+ return getFormData()
+ },
+)
+```
+
+Finally, we'll use `getFormDataFromServer` in our loader to get the state from our server into our client and `handleForm` in our client-side form component.
+
+```tsx app/routes/index.tsx
+import { createFileRoute } from '@tanstack/react-router'
+import {
+ mergeForm,
+ useForm,
+ useStore,
+ useTransform,
+} from '@tanstack/react-form-start'
+
+export const Route = createFileRoute('/')({
+ component: Home,
+ loader: async () => ({
+ state: await getFormDataFromServer(),
+ }),
+})
+
+function Home() {
+ const { state } = Route.useLoaderData()
+ const form = useForm({
+ ...formOpts,
+ transform: useTransform((baseForm) => mergeForm(baseForm, state), [state]),
+ })
+
+ const formErrors = useStore(form.store, (formState) => formState.errors)
+
+ return (
+
+ value < 8 ? 'Client validation: You must be at least 8' : undefined,
+ }}
+ >
+ {(field) => {
+ return (
+
+ )
+ }}
+
+ [formState.canSubmit, formState.isSubmitting]}
+ >
+ {([canSubmit, isSubmitting]) => (
+
+ )}
+
+
+ )
+}
+```
+
+### useTransform
+
+you may have noticed util function `useTransform` being used throughout these examples, it's primary responsibility is the merging of the server and client state. Under the hood it is a useCallback whose deps are that of the server state, when the server state changes it will automatically patch the client state.
+
+```tsx
+const form = useForm({
+ ...formOpts,
+ transform: useTransform((baseForm) => mergeForm(baseForm, state), [state]),
+})
+```
diff --git a/docs/framework/solid/guides/submission-handling.md b/docs/framework/solid/guides/submission-handling.md
index 0ff523e1c..25d095349 100644
--- a/docs/framework/solid/guides/submission-handling.md
+++ b/docs/framework/solid/guides/submission-handling.md
@@ -96,3 +96,20 @@ const form = createForm(() => ({
},
}))
```
+
+> In a situation where you want to be able to submit in an invalid state `canSubmitWhenInvalid` boolean flag can be provided to useForm.
+
+```tsx
+import { createForm } from '@tanstack/solid-form'
+
+const form = createForm(() => ({
+ defaultValues: {
+ data: '',
+ },
+ canSubmitWhenInvalid: true,
+ onSubmit: async ({ value }) => {
+ // Do something with the values
+ console.log(value)
+ },
+}))
+```
diff --git a/docs/framework/vue/guides/submission-handling.md b/docs/framework/vue/guides/submission-handling.md
index 2976e0b85..224d1e2e4 100644
--- a/docs/framework/vue/guides/submission-handling.md
+++ b/docs/framework/vue/guides/submission-handling.md
@@ -97,3 +97,23 @@ const form = useForm({
```
+
+> In a situation where you want to be able to submit in an invalid state `canSubmitWhenInvalid` boolean flag can be provided to useForm.
+
+```vue
+
+
+
+
+```