Skip to content

Commit 72d970a

Browse files
crutchcornautofix-ci[bot]LeCarbonator
authored
Fix issues with SSR (#1890)
* test: add failing test for merge-form behavior * chore: attempt 1 * chore: attempt 2 * chore: attempt 3 * chore: attempt 4 * chore: attempt 5 * CHORE!: REMOVE THIS ATTEMP * chore: attempt 6 * WIP: TO REVERT: Temp replace Next action with failing usage * chore: add logs * chore: attempt 7 * chore: demo working version * feat: move to useMerge away from useTransform * chore: fix remix and start * ci: apply automated fixes and generate docs * chore: attempt 8 * chore: attempt to fix Start and Remix as well * ci: apply automated fixes and generate docs * chore: add our own deepCopy impl * ci: apply automated fixes and generate docs * chore: fix eslint * chore: fix more issues * ci: apply automated fixes and generate docs * fix: JS disabled SSR envs should work now * test: add tests for SSR mergeForm * fix: handle field errors from transform * chore: remove log * chore: fix props * test: add React unit test * chore: refactor to remove setTimeout * Update packages/form-core/src/utils.ts Co-authored-by: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> * chore: add changeset Things should now work as-expected in many more scenarios than before. * ci: apply automated fixes and generate docs --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com>
1 parent e954177 commit 72d970a

File tree

30 files changed

+1072
-341
lines changed

30 files changed

+1072
-341
lines changed

.changeset/swift-ads-warn.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@tanstack/form-core': patch
3+
'@tanstack/react-form-nextjs': patch
4+
'@tanstack/react-form-remix': patch
5+
'@tanstack/react-form-start': patch
6+
'@tanstack/react-form': patch
7+
'@tanstack/form-example-react-next-server-actions-zod': patch
8+
'@tanstack/form-example-react-next-server-actions': patch
9+
---
10+
11+
Fix various issues with SSR. Things should now work as-expected in many many more scenarios than before
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: true,
4+
}
5+
6+
export default nextConfig
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@tanstack/form-example-react-next-server-actions-zod",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"_test:types": "tsc"
9+
},
10+
"dependencies": {
11+
"@tanstack/react-form-nextjs": "^1.28.0",
12+
"@tanstack/react-store": "^0.8.0",
13+
"next": "16.0.5",
14+
"react": "^19.0.0",
15+
"react-dom": "^19.0.0",
16+
"zod": "^3.25.76"
17+
},
18+
"devDependencies": {
19+
"@types/node": "^24.1.0",
20+
"@types/react": "^19.0.7",
21+
"@types/react-dom": "^19.0.3",
22+
"typescript": "5.8.2"
23+
}
24+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use server'
2+
3+
import {
4+
ServerValidateError,
5+
createServerValidate,
6+
} from '@tanstack/react-form-nextjs'
7+
import { z } from 'zod'
8+
import { formOpts } from './shared-code'
9+
10+
const schema = z.object({
11+
age: z.coerce.number().min(12),
12+
firstName: z.string(),
13+
})
14+
15+
const serverValidate = createServerValidate({
16+
...formOpts,
17+
onServerValidate: schema,
18+
})
19+
20+
export default async function someAction(prev: unknown, formData: FormData) {
21+
try {
22+
const validatedData = await serverValidate(formData)
23+
console.log('validatedData', validatedData)
24+
// Persist the form data to the database
25+
// await sql`
26+
// INSERT INTO users (name, email, password)
27+
// VALUES (${validatedData.name}, ${validatedData.email}, ${validatedData.password})
28+
// `
29+
} catch (e) {
30+
if (e instanceof ServerValidateError) {
31+
return e.formState
32+
}
33+
34+
// Some other error occurred while validating your form
35+
throw e
36+
}
37+
38+
// Your form has successfully validated!
39+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use client'
2+
3+
import { useActionState } from 'react'
4+
import {
5+
initialFormState,
6+
mergeForm,
7+
useForm,
8+
useTransform,
9+
} from '@tanstack/react-form-nextjs'
10+
import { z } from 'zod'
11+
import someAction from './action'
12+
import { formOpts } from './shared-code'
13+
14+
export const ClientComp = () => {
15+
const [state, action] = useActionState(someAction, initialFormState)
16+
17+
const form = useForm({
18+
...formOpts,
19+
transform: useTransform(
20+
(baseForm) => mergeForm(baseForm, state ?? {}),
21+
[state],
22+
),
23+
})
24+
25+
return (
26+
<form action={action as never} onSubmit={() => form.handleSubmit()}>
27+
<form.Field
28+
name="age"
29+
validators={{
30+
onChange: z.coerce.number().min(8),
31+
}}
32+
>
33+
{(field) => {
34+
return (
35+
<div>
36+
<input
37+
name="age"
38+
type="number"
39+
value={field.state.value}
40+
onChange={(e) => field.handleChange(e.target.valueAsNumber)}
41+
/>
42+
{field.state.meta.errors.map((error) => (
43+
<p key={error?.message ?? ''}>{error?.message}</p>
44+
))}
45+
</div>
46+
)
47+
}}
48+
</form.Field>
49+
<form.Subscribe
50+
selector={(formState) => [formState.canSubmit, formState.isSubmitting]}
51+
>
52+
{([canSubmit, isSubmitting]) => (
53+
<button type="submit" disabled={!canSubmit}>
54+
{isSubmitting ? '...' : 'Submit'}
55+
</button>
56+
)}
57+
</form.Subscribe>
58+
</form>
59+
)
60+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Metadata } from 'next'
2+
3+
export const metadata: Metadata = {
4+
title: 'Create Next App',
5+
description: 'Generated by create next app',
6+
}
7+
8+
export default function RootLayout({
9+
children,
10+
}: {
11+
children: React.ReactNode
12+
}) {
13+
return (
14+
<html lang="en">
15+
<body>{children}</body>
16+
</html>
17+
)
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ClientComp } from './client-component'
2+
3+
export default function Home() {
4+
return (
5+
<>
6+
<ClientComp />
7+
</>
8+
)
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { formOptions } from '@tanstack/react-form-nextjs'
2+
3+
export const formOpts = formOptions({
4+
defaultValues: {
5+
firstName: '',
6+
age: 0,
7+
},
8+
})

0 commit comments

Comments
 (0)