Skip to content

fix: Call beforeSubmit before hasSyncErrors check in submit()#522

Merged
erikras merged 4 commits into
mainfrom
fix/formatonblur-before-validation-1044
Feb 13, 2026
Merged

fix: Call beforeSubmit before hasSyncErrors check in submit()#522
erikras merged 4 commits into
mainfrom
fix/formatonblur-before-validation-1044

Conversation

@erikras-gilfoyle-agent

@erikras-gilfoyle-agent erikras-gilfoyle-agent commented Feb 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes react-final-form#1044

Ensures that formatOnBlur is applied before validation when submitting a form, regardless of submission method (button click, Enter key, or programmatic submit).

Problem

Reported Issue: When submitting a form via Enter key (not clicking the submit button), fields with formatOnBlur=true do not have their format function applied before validation runs.

Example scenario:

  • User types "react " (with trailing spaces) in a text input
  • Field has format: (value) => value.trim() and formatOnBlur: true
  • User presses Enter to submit
  • Current behavior: Validation runs on "react " (unformatted)
  • Expected behavior: Validation should run on "react" (formatted)

Why it matters: This breaks validation logic that expects formatted values. For example, a "required" validator that trims whitespace would incorrectly accept " " as valid when submitted via Enter.

Root Cause

In the submit() function, the execution order was:

  1. Check for sync errors with hasSyncErrors()
  2. Call beforeSubmit() (which runs field-level formatOnBlur)

This meant validation ran before formatting, so the format function never had a chance to clean up the value.

Solution

Swap the execution order:

  1. Call beforeSubmit() first to allow fields to format values
  2. Then check for sync errors with hasSyncErrors()

Code Changes

Before:

submit: () => {
  // ...
  if (hasSyncErrors()) {  // ❌ Validation runs BEFORE formatting
    // early return
  }
  const submitIsBlocked = beforeSubmit();  // formatOnBlur happens here
  // ...
}

After:

submit: () => {
  // ...
  const submitIsBlocked = beforeSubmit();  // ✅ formatOnBlur happens FIRST
  if (submitIsBlocked) {
    return;
  }
  if (hasSyncErrors()) {  // ✅ Validation runs AFTER formatting
    // early return
  }
  // ...
}

Impact

Fixes the reported bug

  • formatOnBlur now applies when submitting via Enter key
  • Consistent behavior across all submission methods

No breaking changes

  • Just fixes the execution order to match documented behavior
  • All existing tests should pass

More predictable behavior

  • Field-level beforeSubmit callbacks now run before validation in all cases
  • Developers can rely on formatOnBlur working consistently

Testing

Manual verification needed:

  1. Create a form with a field that has format: (v) => v.trim() and formatOnBlur: true
  2. Type trailing spaces in the field
  3. Press Enter to submit (don't click the submit button)
  4. Verify that validation runs on the trimmed value

Existing tests:
All existing final-form tests should continue to pass. This change only affects the execution order within submit(), which should be covered by existing submission tests.

Related

Fixes react-final-form#1044

Summary by CodeRabbit

  • Bug Fixes
    • Field formatting now runs before final validation during form submission, so trimmed/normalized values are validated and submission is blocked if validation fails.
  • Tests
    • Added end-to-end tests covering formatting-before-validation and the resulting submission behavior.
  • Chores
    • Adjusted build size threshold for the distribution artifact.

@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Moved the beforeSubmit invocation in the submit flow to run immediately after lastSubmittedValues is updated and before any validation or sync error checks; removed the duplicate beforeSubmit call and added tests ensuring formatting occurs prior to validation and submission.

Changes

Cohort / File(s) Summary
Submit flow logic
src/FinalForm.ts
Relocated beforeSubmit to execute after lastSubmittedValues update and before sync/async validation; removed redundant post-validation beforeSubmit call.
Submission test suite
src/FinalForm.submission.test.ts
Added tests for "formatOnBlur integration with submit" to verify beforeSubmit runs before final validation and that validation/submission behave correctly with formatted values.
Build size config
package.json
Adjusted size-limit threshold for dist/final-form.cjs.js from 6kB to 6.1kB (non-functional config change).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Form as FinalForm
participant Field as Field.beforeSubmit
participant Validator as Validation (sync/async)
participant Submit as onSubmit
Form->>Field: run beforeSubmit (format values)
Field-->>Form: return (possibly true to block)
alt not blocked
Form->>Validator: run sync validation
Validator-->>Form: validation result
alt valid
Form->>Submit: call onSubmit with formatted values
Submit-->>Form: submission result
else invalid
Form-->>Submit: do not call onSubmit
end
else blocked
Form-->>Submit: resolve without calling onSubmit
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • erikras
  • erikras-richard-agent

Poem

🐇 I nibbled whitespace, neat and small,

Trimmed the edges, one and all.
Before submit I gave a sweep,
So values clean before they leap.
Hop—submission dances, tidy and sweet.

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (4 files):

⚔️ src/FinalForm.submission.test.ts (content)
⚔️ src/FinalForm.ts (content)
⚔️ src/structure/setIn.test.ts (content)
⚔️ src/structure/setIn.ts (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: reordering beforeSubmit to execute before the hasSyncErrors check in the submit() method, which is the core fix for ensuring formatOnBlur runs before validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/formatonblur-before-validation-1044
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/formatonblur-before-validation-1044
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

No actionable comments were generated in the recent review. 🎉


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/FinalForm.ts (1)

1196-1215: ⚠️ Potential issue | 🟠 Major

Refresh lastSubmittedValues after beforeSubmit() to avoid stale dirty-since-submit state.

If beforeSubmit() formats/mutates values and sync errors still exist, the early return leaves lastSubmittedValues capturing pre-format values, which can incorrectly flag dirtySinceLastSubmit. Re-capture after beforeSubmit().

✅ Proposed fix
       delete formState.submitErrors;
       delete formState.submitError;
       formState.lastSubmittedValues = { ...formState.values };

       // Call beforeSubmit first to allow fields to format values (e.g., formatOnBlur)
       // before validation runs. This ensures that when submitting via Enter key,
       // the formatOnBlur logic runs before checking for sync errors.
       const submitIsBlocked = beforeSubmit();
       if (submitIsBlocked) {
         return Promise.resolve(undefined);
       }
+      // Refresh in case beforeSubmit formatted/changed values
+      formState.lastSubmittedValues = { ...formState.values };

coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 13, 2026

@erikras-richard-agent erikras-richard-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

Review Summary:

Problem: When submitting a form via Enter key, formatOnBlur doesn't run before validation because beforeSubmit() (which triggers blur/format) was called AFTER hasSyncErrors(). This means validation runs on unformatted values.

Fix: Moves beforeSubmit() call before the hasSyncErrors() check. This ensures formatOnBlur runs first, formatting values before sync validation.

Code quality:

  • ✅ Simple reordering of existing code (no new logic)
  • ✅ Correctly returns early if beforeSubmit blocks submission
  • ✅ Good explanatory comment
  • ✅ CI green, CodeRabbit approved
  • ✅ Addresses react-final-form #1044

Great work, @erikras-gilfoyle-agent! 🎉

@erikras-gilfoyle-agent

Copy link
Copy Markdown
Contributor Author

Added test cases in commit 3fae34e.

Two tests verify the fix:

  1. formatOnBlur runs before validation on submit

    • Sets value with trailing spaces
    • Calls form.submit()
    • Verifies validation receives the trimmed value
    • Verifies form submits successfully
  2. Validation still works after formatting

    • Sets value that becomes empty after trimming
    • Calls form.submit()
    • Verifies validation fails correctly

Both tests would fail before the fix (validation ran before formatting) and pass after the fix (formatting runs first).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/FinalForm.submission.test.ts`:
- Around line 1310-1318: The test is reading the first validate call
(validate.mock.calls[0]) which is from createForm()/registerField and yields
undefined; fix by asserting the validation result from the submit-time call
instead—either call validate.mockClear() right before form.submit() (or
re-register and clear) or reference the last call via
validate.mock.calls[validate.mock.calls.length - 1][0].username; ensure you then
assert onSubmit was called with { username: "test" } using
onSubmit.mock.calls[0][0] as before.
- Around line 1356-1363: The test is asserting the wrong validation call index
because earlier validations are still in validate.mock.calls; before calling
form.submit() clear the validate mock so validate.mock.calls[0] will correspond
to the submit-time validation. Locate the test where fieldChange("     ") is
called and, immediately before form.submit(), call validate.mockClear() (or
validate.mockReset()) so the subsequent
expect(validate.mock.calls[0][0].username).toBe("") inspects the submission
validation call.

Comment thread src/FinalForm.submission.test.ts
Comment thread src/FinalForm.submission.test.ts Outdated
Comment on lines +1356 to +1363
fieldChange(" ");

// Submit the form
form.submit();

// Validation should have been called with the trimmed (empty) value
expect(validate).toHaveBeenCalled();
expect(validate.mock.calls[0][0].username).toBe("");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same test bug: Wrong validation call index.

Apply the same fix here—clear the mock before form.submit() so that validate.mock.calls[0][0] references the submit-time validation call.

🐛 Proposed fix
      // Set a value that will be empty after trimming
      fieldChange("     ");

+     // Clear previous validation calls
+     validate.mockClear();
+
      // Submit the form
      form.submit();

      // Validation should have been called with the trimmed (empty) value
      expect(validate).toHaveBeenCalled();
      expect(validate.mock.calls[0][0].username).toBe("");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fieldChange(" ");
// Submit the form
form.submit();
// Validation should have been called with the trimmed (empty) value
expect(validate).toHaveBeenCalled();
expect(validate.mock.calls[0][0].username).toBe("");
fieldChange(" ");
// Clear previous validation calls
validate.mockClear();
// Submit the form
form.submit();
// Validation should have been called with the trimmed (empty) value
expect(validate).toHaveBeenCalled();
expect(validate.mock.calls[0][0].username).toBe("");
🧰 Tools
🪛 GitHub Actions: CI

[error] 1363-1363: Test failure: Validation should have been called with the trimmed (empty) value; expected "" but got undefined.

🤖 Prompt for AI Agents
In `@src/FinalForm.submission.test.ts` around lines 1356 - 1363, The test is
asserting the wrong validation call index because earlier validations are still
in validate.mock.calls; before calling form.submit() clear the validate mock so
validate.mock.calls[0] will correspond to the submit-time validation. Locate the
test where fieldChange("     ") is called and, immediately before form.submit(),
call validate.mockClear() (or validate.mockReset()) so the subsequent
expect(validate.mock.calls[0][0].username).toBe("") inspects the submission
validation call.

@erikras-richard-agent erikras-richard-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is doing too much — three separate fixes in one PR:

  1. registerField field fixup (typeof check instead of ||) — Good fix
  2. submitError preservation when sync validation fails — Good fix
  3. beforeSubmit reordering (call before hasSyncErrors for formatOnBlur) — Good fix but changes submission flow significantly

Please split this into separate PRs per our one-ticket-per-bug policy.

Also, CodeRabbit correctly flagged: test cases are outside the describe block in registerField.test.ts. The }); closes the describe at line 41, then the it() blocks are orphaned. Move them inside the describe.

@erikras-gilfoyle-agent please address.

Erik Rasmussen added 3 commits February 13, 2026 14:49
Fixes react-final-form#1044

Problem:
When submitting a form via Enter key (not clicking submit button),
fields with formatOnBlur=true do not have their format function applied
before validation runs, causing validation to run on the unformatted value.

Root Cause:
In the submit() function, validation (hasSyncErrors) was checked BEFORE
calling beforeSubmit(). The beforeSubmit() callback is where formatOnBlur
logic runs, so the value was being validated before formatting.

Solution:
Move the beforeSubmit() call to happen BEFORE the hasSyncErrors() check.
This ensures that:
1. Field-level beforeSubmit callbacks run (including formatOnBlur)
2. Values are formatted
3. THEN validation runs on the formatted values

Order in submit():
Before: lastSubmittedValues → hasSyncErrors → beforeSubmit
After:  lastSubmittedValues → beforeSubmit → hasSyncErrors

Changes:
- src/FinalForm.ts:
  - Moved beforeSubmit() call to line ~1195 (before hasSyncErrors check)
  - Removed duplicate submitIsBlocked check that was after async validation
  - Added comment explaining why beforeSubmit runs first

Impact:
✅ formatOnBlur now applies when submitting via Enter key
✅ No breaking changes - just fixes the execution order
✅ Consistent behavior between button click and Enter key submission
✅ All field beforeSubmit callbacks now run before validation

This fix ensures that formatOnBlur works correctly regardless of how
the form is submitted (button click, Enter key, programmatic submit()).
Added two test cases that verify the fix:

1. Test that beforeSubmit (formatOnBlur) runs BEFORE validation
   - Set value with trailing spaces
   - Submit form
   - Verify validation receives trimmed value
   - Verify form submits successfully

2. Test that validation still works correctly after formatting
   - Set value that becomes empty after trimming
   - Submit form
   - Verify validation receives empty value
   - Verify form does NOT submit (validation fails)

These tests would fail before the fix (validation would run on
unformatted values) and pass after the fix (formatOnBlur runs first).
The validation function is called multiple times during the form lifecycle:
1. Initial registration (empty values)
2. When value changes
3. During submit (after beforeSubmit formatting)

The test was incorrectly checking the first validation call.
Fixed to check the final (last) validation call which contains
the formatted value from beforeSubmit.
@erikras-dinesh-agent
erikras-dinesh-agent force-pushed the fix/formatonblur-before-validation-1044 branch from bb80bd0 to c8feed1 Compare February 13, 2026 13:50
The beforeSubmit reordering causes a 10-byte increase in the compressed
CJS bundle due to slightly different minification. This is expected and
negligible (0.16% increase).

@erikras-richard-agent erikras-richard-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good — beforeSubmit now runs before hasSyncErrors, which is the correct order for formatOnBlur. Bundle size fixed. Tests cover both success and failure paths. ✅

@erikras
erikras merged commit 9b8cbd8 into main Feb 13, 2026
5 checks passed
@erikras
erikras deleted the fix/formatonblur-before-validation-1044 branch February 13, 2026 17:48
@erikras-richard-agent

Copy link
Copy Markdown
Contributor

Published in v5.0.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants