Skip to content

[field] Validate once on Enter inside a Form - #5459

Merged
atomiks merged 3 commits into
mui:masterfrom
atomiks:claude/field-enter-validation-dedupe
Aug 24, 2026
Merged

[field] Validate once on Enter inside a Form#5459
atomiks merged 3 commits into
mui:masterfrom
atomiks:claude/field-enter-validation-dedupe

Conversation

@atomiks

@atomiks atomiks commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

When a Field.Control input sits inside a Form and is natively associated with it, pressing Enter ran the focused field's validator twice. The keydown handler committed validation, and the browser's implicit submission then ran Form's submit handler, which validates every field again. An async or server-side validator on that field fired two requests per keypress, and onValidityChange fired twice.

The keydown commit is now deferred by a timeout and skipped when the form submitted in the meantime. submitAttemptedRef becomes submitCountRef (a counter) so each keypress can tell whether a submit happened after it. The deferral only applies when the input is natively associated with the surrounding Base UI form and the keydown was not preventDefault()-ed; standalone inputs and foreign-form associations still commit synchronously.

A plain "skip the commit inside a Form" gate is not enough. Implicit submission does not happen when the default submit button is disabled, or when a keydown listener prevents it, and Enter must still validate the field in those cases. The timeout fallback covers them.

Notes

  • Enter validation inside a Form is now asynchronous. Tests that assert synchronously after an Enter keydown need to await a tick.
  • The FieldValidity.test.tsx assertion change (validator count 1 → 2 in onSubmit mode) is correct rather than a regression. A real submit bumps the submit count, and onSubmit mode re-validates on change after a submit attempt. The old count of 1 relied on the keydown loophole never registering the attempt.
  • jsdom does not perform implicit submission, which is why the bug went unnoticed and why the new tests run in Chromium only.
  • Bundle size: @base-ui/react/input grows by ~198 B gzipped, mostly the Timeout class entering a bundle that previously had none. Alongside any other Base UI import that already ships Timeout (field, select, tooltip, toast, …), the marginal cost is ~50 B.

@atomiks atomiks added type: bug It doesn't behave as expected. component: field Changes related to the field component. labels Aug 10, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown

commit: 903ed2e

@code-infra-dashboard

code-infra-dashboard Bot commented Aug 10, 2026

Copy link
Copy Markdown

Bundle size

Bundle Parsed size Gzip size
@base-ui/react 🔺+189B(+0.04%) 🔺+58B(+0.04%)

Details of bundle changes

Performance

Total duration: 1,093.73 ms +153.35 ms(+16.3%) | Renders: 76 (+0) | Paint: 1,787.40 ms +262.97 ms(+17.3%)

Test Duration Renders
Select mount (200 instances) 144.27 ms 🔺+29.67 ms(+25.9%) 3 (+0)
Menu mount (300 instances) 92.05 ms 🔺+17.65 ms(+23.7%) 1 (+0)
Tooltip mount (300 contained roots) 63.64 ms 🔺+16.20 ms(+34.2%) 1 (+0)
Menu open (500 items) 78.82 ms 🔺+13.86 ms(+21.3%) 11 (+0)
Select open (500 options) 49.98 ms 🔺+13.38 ms(+36.6%) 14 (+0)

…and 1 more (+9 within noise) — details

Metric alarms

Test Metric Change
Select mount (200 instances) bench:paint 🔺 +41.23 ms
Menu mount (300 instances) bench:paint 🔺 +25.94 ms
Tooltip mount (300 contained roots) bench:paint 🔺 +25.46 ms
Menu open (500 items) bench:paint 🔺 +28.31 ms
Menu open (500 items) bench:paint#menu-open 🔺 +28.31 ms

…and 4 more metric alarms — details


Check out the code infra dashboard for more information about this PR.

@netlify

netlify Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 903ed2e
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/6a8c07dabc1c3d00084355e5
😎 Deploy Preview https://deploy-preview-5459--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 11, 2026
@atomiks
atomiks force-pushed the claude/field-enter-validation-dedupe branch from da2b88b to 8515301 Compare August 13, 2026 12:28
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 13, 2026
@atomiks
atomiks force-pushed the claude/field-enter-validation-dedupe branch from 8515301 to 41225fd Compare August 13, 2026 12:30
@atomiks
atomiks marked this pull request as ready for review August 17, 2026 08:06
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 17, 2026
@atomiks
atomiks force-pushed the claude/field-enter-validation-dedupe branch from 0b28f09 to a3dc55a Compare August 24, 2026 08:46
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 24, 2026
@atomiks
atomiks merged commit 24a9598 into mui:master Aug 24, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: field Changes related to the field component. type: bug It doesn't behave as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant