Skip to content

Fix #487: Track dirty state for unregistered fields (FieldArray)#528

Merged
erikras merged 4 commits into
mainfrom
fix/issue-487-dirty-state-only
Feb 13, 2026
Merged

Fix #487: Track dirty state for unregistered fields (FieldArray)#528
erikras merged 4 commits into
mainfrom
fix/issue-487-dirty-state-only

Conversation

@erikras-richard-agent

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

Copy link
Copy Markdown
Contributor

Fixes #487

Problem

When using FieldArray, only individual array items are registered as fields, not the array itself. When all items are removed, no registered fields exist, so the form incorrectly shows pristine=true/dirty=false even though the array value changed from initialValues.

Root cause: Form only checked dirty state for registered fields, ignoring changes to unregistered fields.

Solution

After checking registered fields, also check all unregistered top-level keys in values vs initialValues using Set deduplication for performance. This ensures that changes to unregistered fields (like the array field when using FieldArray) are properly tracked as dirty.

Changes

  • In calculateNextFormState, check unregistered fields for dirty state
  • Use Set to dedupe keys from both values and initialValues (performance optimization)
  • Use shallowEqual to compare unregistered field values
  • Only applies to unregistered fields (preserves custom isEqual for registered)

Tests

Added test to verify form remains dirty after removing all array items when the array field itself is not registered.

All existing tests pass (342/342).

Note

This is a clean rewrite of #525, focusing only on the #487 fix without the #482 changes.

Summary by CodeRabbit

  • Bug Fixes

    • Dirty/pristine tracking updated so unregistered fields (including removing items from array fields) correctly keep the form dirty when appropriate.
  • Tests

    • Added tests validating dirty/pristine behavior during removals from unregistered array fields.
  • Chores

    • Increased bundle size limits for distributable outputs.

Erik Rasmussen added 2 commits February 13, 2026 14:37
When using FieldArray, only individual array items are registered as fields,
not the array itself. When all items are removed, no registered fields exist,
so the form incorrectly shows pristine=true/dirty=false even though the array
value changed from initialValues.

Root cause: Form only checked dirty state for registered fields, ignoring
changes to unregistered fields.

Solution: After checking registered fields, also check all unregistered
top-level keys in values vs initialValues. This ensures that changes to
unregistered fields (like the array field when using FieldArray) are
properly tracked as dirty.

Changes:
- In calculateNextFormState, check unregistered fields for dirty state
- Use shallowEqual to compare unregistered field values
- Only applies to unregistered fields (preserves custom isEqual for registered)

Added test to verify form remains dirty after removing all array items when
the array field itself is not registered.

All existing tests pass (340/340).
Address CodeRabbit feedback: use Set to collect unique keys from both
values and initialValues, avoiding redundant allocations and comparisons
while preserving existing dirty-check behavior.
@coderabbitai

coderabbitai Bot commented Feb 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds fallback dirtiness detection inside calculateNextFormState to compare unregistered keys between formState.values and formState.initialValues, ensuring forms remain dirty when unregistered fields (e.g., FieldArray items) change or are removed.

Changes

Cohort / File(s) Summary
Dirtiness Check Enhancement
src/FinalForm.ts
Adds fallback logic when no registered field is dirty: builds a deduplicated key set from formState.values and formState.initialValues, compares unregistered keys with shallowEqual, and flips dirty/pristine on first difference.
Test Coverage
src/FinalForm.validating.test.ts
Adds test suite "FinalForm.dirty - Issue #487" verifying dirty/pristine transitions when removing items from an unregistered array, including removal of the final item.
Bundle Size Config
package.json
Increases size-limit thresholds for dist/final-form.es.js and dist/final-form.cjs.js from 6kB to 6.5kB (configuration-only change).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled through values and counted each key,

When arrays went hollow, I shouted "Not free!"
I checked what was missing, compared what remained,
Found the tiny difference, and flagged what had changed.
Hooray — dirty stays dirty, the mystery explained.

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
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.
✅ Passed checks (5 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: fixing issue #487 by tracking dirty state for unregistered fields like FieldArray.
Linked Issues check ✅ Passed The pull request directly addresses issue #487 by implementing dirty state tracking for unregistered fields (FieldArray keys), with code changes to calculateNextFormState and a test verifying the fix.
Out of Scope Changes check ✅ Passed All changes are scoped to issue #487: FinalForm.ts adds unregistered field dirty-check logic, test file adds verification, and package.json updates bundle size limits due to code additions.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-487-dirty-state-only

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

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

@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)

592-624: ⚠️ Potential issue | 🟠 Major

Custom isEqual can be bypassed for nested fields

When foundDirty is false (Line 608), the new top‑level shallowEqual comparison can mark the form dirty even if all registered nested fields are “pristine” per their custom isEqual. Example: a registered field user.name with a case‑insensitive isEqual would still flip the form to dirty because the top‑level user object differs. If preserving custom isEqual semantics is required, consider scoping the fallback to only unregistered structural changes (e.g., array length deltas) or otherwise excluding paths covered by registered fields with custom isEqual, and add a regression test to confirm the intended behavior.

@erikras-richard-agent

Copy link
Copy Markdown
Contributor Author

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.

Form not dirty after removing items from FieldArray using final-form-arrays

2 participants