feat(react-form): correctly handle client-side validation with NextJS server-action
Currently, there is no good, standardized way to properly handle client-side validation along with NextJS server-actions. The TanStack form supports server-actions, but the current implementation has one huge drawback: the server-action calls even if the client-side validation fails.
Client-side validation when submitting a form to a server-action is very important, especially for the user experience. The user sees validation errors immediately, instead of waiting for a response from the server. In addition, there is no reason to call a server-action if the client-side validation fails. The server will perform the same validation that takes place on the client side to eventually fail and return an error, so what is the purpose of pointlessly burdening the server?
So the reason for this feature is to make client-side validation with server-actions finally pleasant.
To prevent the server-action from executing, event.preventDefault() must be called synchronously. The problem is that form validation can also happen asynchronously, so there is no easy way to handle this. So event.preventDefault() calls immediately, then form validation occurs, and if successful, event.target.requestSubmit() is called to call onSubmit again. In the next iteration of onSubmit the code knows that it has already been called and the validation has passed, so it does not call event.preventDefault() and the server-action can be executed.
Some people have tried to implement this simply by calling event.target.submit() after successful validation, but with this approach the page is refreshed, so it completely fails with NextJS. I also noticed that calling onSubmit again doesn't work without this line: await new Promise((resolve) => setTimeout(resolve, 0)), so it's just a weird trick to make it work.
Stopping the execution of the server-action must be done in onSubmit, because then we do not lose the progressive enhancement: if the form data is invalid and JavaScript is enabled, then validation is done on the client side, and the server-action call is blocked. Otherwise, onSubmit cannot be called by the lack of JavaScript, so the server-action is called immediately, and all validation is done on the server, so everything still works correctly.
I'm also wondering if it's a good idea to mutate the useForm API, since this assigns the NextJS-based handleActionSubmit implementation to every other framework. It will not be possible to modify this code for any other specific framework, so this code should lie and be accessible only through @tanstack/react-form/nextjs. Maybe this approach is better?:
import { useForm } from '@tanstack/react-form';
import { useActionSubmit } from '@tanstack/react-form/nextjs';
import { action } from './actions';
export const ExampleForm = () => {
const form = useForm({ ... });
const handleActionSubmit = useActionSubmit(form);
return (
<form action={action} onSubmit={handleActionSubmit}>
...
</form>
);
};
View your CI Pipeline Execution ↗ for commit 5f2678b705cb5c74f273110ad9a1c1721984156d.
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 1m 23s | View ↗ |
nx run-many --target=build --exclude=examples/** |
✅ Succeeded | 8s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-03-28 10:59:04 UTC
More templates
- @tanstack/form-example-angular-array
- @tanstack/form-example-angular-simple
- @tanstack/form-example-lit-simple
- @tanstack/form-example-lit-ui-libraries
- @tanstack/form-example-react-array
- @tanstack/form-example-react-compiler
- @tanstack/field-errors-from-form-validators
- @tanstack/form-example-react-large-form
- @tanstack/form-example-react-next-server-actions
- @tanstack/form-example-react-query-integration
- @tanstack/form-example-remix
- @tanstack/form-example-react-simple
- @tanstack/form-example-react-standard-schema
- @tanstack/form-example-react-tanstack-start
- @tanstack/form-example-react-ui-libraries
- @tanstack/form-example-solid-array
- @tanstack/form-example-solid-simple
- @tanstack/form-example-vue-array
- @tanstack/form-example-vue-simple
@tanstack/angular-form
npm i https://pkg.pr.new/@tanstack/angular-form@1299
@tanstack/form-core
npm i https://pkg.pr.new/@tanstack/form-core@1299
@tanstack/react-form
npm i https://pkg.pr.new/@tanstack/react-form@1299
@tanstack/lit-form
npm i https://pkg.pr.new/@tanstack/lit-form@1299
@tanstack/solid-form
npm i https://pkg.pr.new/@tanstack/solid-form@1299
@tanstack/vue-form
npm i https://pkg.pr.new/@tanstack/vue-form@1299
commit: 5f2678b