Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handleSubmit does not always mark all fields as touched #1360

Open
LeCarbonator opened this issue Mar 31, 2025 · 3 comments · May be fixed by #1367
Open

handleSubmit does not always mark all fields as touched #1360

LeCarbonator opened this issue Mar 31, 2025 · 3 comments · May be fixed by #1367

Comments

@LeCarbonator
Copy link
Contributor

Describe the bug

When calling <useForm>.handleSubmit() or <useAppForm>.handleSubmit(), all fields being touched is not consistent when used with form-level validators.

That is because of an early return statement that is sensible in theory, but has problems in edge cases.

Example

I have two fields, requiredName and optional. When using a standard schema and passing it to <useForm> as onChange validator, it will validate requiredName when changing optional. That makes complete sense.

However, that means that canSubmit can become false even if no required field has ever been touched. This results in submission handling returning early without marking all the fields as touched.

The Stackblitz contains examples of this behaviour.

Use case

My use case is that I only show errors on touched fields. This prevents multiple errors to show up when modifying an optional field (due to schema validation). With canSubmit = false, they will now be stuck in an error state but cannot show it due to being untouched.

Is this intended behaviour?

Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-od8bfy6y?file=src%2FApp.tsx

Steps to reproduce

  1. Go to the stackblitz repro
  2. For each form variant, change the value of the second field
  3. Click the submit button (will always fail)

Expected:

  • Both fields should be marked as isTouched
  • Errors show up for field 1

Actual:

  • onChange or onBlur don't mark field 1 as isTouched
  • onSubmit marks field 1 as isTouched
  • having both onSubmit and any of the other two, field 1 will not be isTouched

Expected behavior

As user, I expect isTouched to be true for all fields when handling the submission, independent from which validator you used.

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

  • Windows 11
  • Firefox v136.0.4

TanStack Form adapter

react-form

TanStack Form version

v1.2.1

TypeScript version

v5.7.2

Additional context

For anyone with the same problem, you can access submissionAttempts with
useStore(field.form.store, state => state.submissionAttempts). This allows you to check if a submission has happened before, overriding any touched condition in your logic.

This state is accessible in both the callback as well as field context.

@LeCarbonator
Copy link
Contributor Author

#1355 appears to be a related issue, but I would like confirmation that this truly is intended behaviour.

@LeCarbonator
Copy link
Contributor Author

Shoff brought up an example with field validators. It's related to the early return that causes this issue, but is not related to the isTouched property. Instead, this repro showcases the field validation not firing if another field has a validation error:

https://stackblitz.com/edit/tanstack-form-ew4q42pd?file=src%2Findex.tsx&preset=node

@ryanelian
Copy link

Can confirm that using submission attempts allows the correct behavior for me:

"use client";

import type { AnyFieldApi, StandardSchemaV1Issue } from "@tanstack/react-form";
import { useStore } from "@tanstack/react-form";

/**
 * Displays the status of a field.
 * 
 * The Standard Schema is compatible with Standard Schema validation libraries such as Zod.
 * 
 * @see https://github.com/standard-schema/standard-schema
 */
export function FieldStatus({ field }: { field: AnyFieldApi }) {
	const submissionAttempts = useStore(field.form.store, state => state.submissionAttempts)

	let errorMessage = "";
	const validate = submissionAttempts > 0 || field.state.meta.isTouched;
	if (validate && field.state.meta.errors.length > 0) {
		const errors = field.state.meta.errors as StandardSchemaV1Issue[];
		errorMessage = errors.map((error) => error.message).join(", ");
	}

	return (
		<>
			{errorMessage && (
				<div className="mt-1 text-sm text-red-600">{errorMessage}</div>
			)}
			{field.state.meta.isValidating && (
				<div className="mt-1 text-sm text-gray-600">Validating...</div>
			)}
		</>
	);
}
	const form = useForm({
		defaultValues: {
			myStuffs: '...',
		},
		validators: {
			onChangeAsync: myValidator,
			onChangeAsyncDebounceMs: 150,
		},
		onSubmit: async ({ value }) => {
			mutation.reset();
			await mutation.mutateAsync({
				myStuffs: '...'
			});
		},
	});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants