Skip to content

Fix #988: Update initialValues when field initialValue prop changes#543

Merged
erikras merged 4 commits into
mainfrom
fix-988-field-initialvalue-dirty
May 5, 2026
Merged

Fix #988: Update initialValues when field initialValue prop changes#543
erikras merged 4 commits into
mainfrom
fix-988-field-initialvalue-dirty

Conversation

@erikras-dinesh-agent

@erikras-dinesh-agent erikras-dinesh-agent commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Fix #988: Update initialValues when field initialValue prop changes

Problem

When using Field-level initialValue props (instead of Form-level initialValues), if the field's initialValue prop changes after submit (e.g., to reflect a newly saved value), the form's initialValues doesn't update. This causes the field to remain dirty even though the current value matches the new "saved" initialValue.

Example Scenario (Radio Buttons)

  1. Field registers with initialValue="A" → Form initialValues = {field: "A"}
  2. User changes to "B" → Form values = {field: "B"}, dirty = true
  3. After submit, Field re-registers with initialValue="B" (reflecting saved value)
  4. BUG: Form initialValues stays as {field: "A"}
  5. Result: dirty calculation: "B" !== "A" = still dirty!

Root Cause

In registerField (line 1002), the condition for updating initialValues was:

if (
  fieldConfig.initialValue !== undefined &&
  (noValueInFormState ||
    currentValue === currentInitialValue)
) {
  // Update initialValues
}

This fails when:

  • noValueInFormState is FALSE (value exists)
  • currentValue ("B") !== currentInitialValue ("A") is FALSE

So the new initialValue prop is ignored!

Solution

Added a second condition that updates initialValues when the field's initialValue prop changes, without overwriting the user's current input:

} else if (
  // Fix #988: Update initialValue when field's initialValue prop changes
  fieldConfig.initialValue !== undefined &&
  fieldConfig.initialValue !== currentInitialValue &&
  currentValue !== undefined
) {
  state.formState.initialValues = setIn(
    state.formState.initialValues || {},
    name as string,
    fieldConfig.initialValue,
  ) as InitialFormValues;
  runValidation(undefined, notify);
}

Tests

Added comprehensive test coverage in src/FinalForm.field-initialValue-988.test.ts:

  1. Basic scenario: Field initialValue changes after user input → initialValues updates, dirty = false
  2. Preserve user input: Field initialValue changes but user has different value → initialValues updates but values unchanged ✅
  3. Radio button scenario: Exact reproduction of issue #988 ✅

All existing tests still pass (367 total).

Related Issue

Fixes final-form/react-final-form#988

Checklist

  • Code follows project style
  • Added comprehensive test coverage
  • All tests pass
  • Handles edge cases (user input preservation)
  • Backward compatible

Summary by CodeRabbit

  • Bug Fixes

    • Prevents user-entered field values from being overwritten when a field's initial value changes; keeps initial/current values and dirty state consistent and refreshes validation.
  • Tests

    • Added tests covering initial-value updates across registration and re-registration flows, including nested fields and radio selections.
  • Chores

    • Slightly increased size-limit thresholds for built artifacts.

When a Field's initialValue prop changes (e.g., after submit when the
saved value updates), the form's initialValues should be updated to
match. Previously, the form would remain dirty even though the current
value matched the new "saved" initialValue.

This fix adds logic to update form.initialValues when:
- Field's initialValue prop changes
- Field already has a value
- Without overwriting the user's current input

Fixes final-form/react-final-form#988
@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec92e0fb-ac1b-45f3-9288-28c36c719730

📥 Commits

Reviewing files that changed from the base of the PR and between d2146f3 and f5c6a47.

📒 Files selected for processing (1)
  • package.json

📝 Walkthrough

Walkthrough

Caches current field value/initialValue during registration, adds logic to update formState.initialValues when a field's initialValue changes on re-registration without overwriting user-entered values, re-runs validation, and adds tests covering unregister/re-register initialValue and dirty-state scenarios.

Changes

Cohort / File(s) Summary
InitialValue test suite
src/FinalForm.field-initialValue-988.test.ts
Adds Jest tests verifying initialValues, values, and dirty across field registration, user changes, unregistration, and re-registration, including nested radio-name cases and both preservation and replacement behaviors.
Core form logic
src/FinalForm.ts
registerField now caches currentValue and currentInitialValue, preserves existing initialization behavior, and adds a branch to update state.formState.initialValues when a re-registered field's initialValue changes while preserving the user's current values; triggers runValidation(undefined, notify) after initialization or update.
Bundle size config
package.json
Small adjustments to size-limit thresholds for built artifacts (dist/final-form.es.js and dist/final-form.cjs.js).

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • erikras
  • erikras-richard-agent

Poem

🐰 I hop through fields where initial values play,
I cache what I find and I do not betray,
If a field comes back with a new starting tune,
I nudge the form state and re-validate soon,
Carrots and tests — a celebratory day!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: updating initialValues when a field's initialValue prop changes, addressing issue #988.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-988-field-initialvalue-dirty

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/FinalForm.field-initialValue-988.test.ts`:
- Around line 45-46: Extract a small helper to return the last emitted form
state instead of repeating spy.mock.calls[...] — add a local function like last
= () => spy.mock.calls.at(-1)[0] and update assertions that currently compute
lastCall (e.g., the occurrences using spy.mock.calls[spy.mock.calls.length -
1][0] and subsequent expect(...initialValues).toEqual(...)) to use last() (e.g.,
expect(last().initialValues).toEqual(...)); apply the same replacement for the
other occurrences referenced (around the other assertions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d48f0f8b-d5e0-4b9c-92ff-830026af0b57

📥 Commits

Reviewing files that changed from the base of the PR and between 4abbb6d and 03123d2.

📒 Files selected for processing (2)
  • src/FinalForm.field-initialValue-988.test.ts
  • src/FinalForm.ts

Comment thread src/FinalForm.field-initialValue-988.test.ts Outdated
@erikras-richard-agent

Copy link
Copy Markdown
Contributor

CodeRabbit Review Comments Addressed

Pushed commit 9fa6946 to address the CodeRabbit review feedback.

Changes Made

File: src/FinalForm.field-initialValue-988.test.ts

Issue: CodeRabbit flagged repeated use of spy.mock.calls[spy.mock.calls.length - 1][0] and const lastCall = spy.mock.calls[...][0] across all three test cases as unnecessarily verbose and hard to scan.

Fix: Extracted a local last helper in each it block:

const last = () => spy.mock.calls.at(-1)[0]

Then replaced all occurrences of the verbose patterns:

  • spy.mock.calls[spy.mock.calls.length - 1][0].valueslast().values
  • const lastCall = spy.mock.calls[spy.mock.calls.length - 1][0] + lastCall.xyzlast().xyz

Applied consistently across all 3 test cases (lines 45–48, 64–70, 82–85, 101–104, 109–112, 124–131).

Test Results

All 367 tests pass ✅

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 30, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 1, 2026
erikras
erikras previously approved these changes May 1, 2026
@erikras
erikras merged commit c7555ee into main May 5, 2026
5 checks passed
@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.

Radio button remains dirty after submit because initialValue does not update to newly saved value despite initialValue prop changing

3 participants