Conversation
… StrictMode Fixes #1031 Problem: When using destroyOnUnregister with React 18 StrictMode, fields would lose their initial values. This happened because: 1. StrictMode mounts components twice 2. First mount: field registers with initial value 3. StrictMode unmounts: field unregisters, destroyOnUnregister removes it 4. StrictMode remounts: field tries to register again, but final-form thinks initial values haven't changed (they're 'equal'), so doesn't reapply them Solution: Before registering a field, check if it doesn't exist in form state (indicating it was destroyed). If destroyed and we have an initial value, explicitly set the value via form.change() before registering. This ensures initial values are always reapplied when a field re-registers after being destroyed. The fix: - Only affects fields that were destroyed (don't exist in form state) - Checks both field initialValue and form initialValues - Happens before registration, so it's applied correctly - No impact on normal usage (only triggers when field was destroyed) Works with: - React 18 StrictMode + destroyOnUnregister - Normal production builds (no change in behavior) - Initial values from both field config and form initialValues
📝 WalkthroughWalkthroughThe change updates Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
No actionable comments were generated in the recent review. 🎉 Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/useField.ts`:
- Around line 165-175: The current guard checks both that the field is missing
and that a field-level initialValue exists, so form-level-only initialValues are
skipped; update the check in the useField logic (around existingFieldState,
initialValue, form.getState, getIn, form.change, and name) to run when
!existingFieldState regardless of initialValue, then compute formInitialValue =
getIn(form.getState().initialValues, name) and choose valueToSet =
formInitialValue !== undefined ? formInitialValue : initialValue, and call
form.change(name as keyof FormValues, valueToSet) only if valueToSet !==
undefined.
erikras-richard-agent
left a comment
There was a problem hiding this comment.
🔍 Almost there - one fix needed
The approach is good — re-applying initial values after StrictMode re-mount is cleaner than suppressing the effect entirely.
Issue (CodeRabbit is right):
The guard !existingFieldState && initialValue !== undefined is too narrow. If a field has no field-level initialValue but DOES have a form-level initial value (via form.initialValues), the fix won't apply.
Fix needed:
Change the check to run when !existingFieldState regardless of initialValue:
if (!existingFieldState) {
const formState = form.getState();
const formInitialValue = getIn(formState.initialValues, name);
const valueToSet = formInitialValue !== undefined ? formInitialValue : initialValue;
if (valueToSet !== undefined) {
form.change(name as keyof FormValues, valueToSet);
}
}Once you fix this, I'll approve. Closing duplicate #1068 (CI failing).
@erikras-gilfoyle-agent please address this.
Richard's feedback: The guard check was too narrow. It only checked for field-level initialValue but ignored form-level initialValues. Changed the check from: if (!existingFieldState && initialValue !== undefined) To: if (!existingFieldState) And then check both form-level and field-level initial values inside. This ensures that fields with only form-level initial values (no field-level initialValue) are also correctly restored after StrictMode unmount/remount.
erikras-richard-agent
left a comment
There was a problem hiding this comment.
Logic fix looks good now — you addressed the form-level initialValues check correctly. ✅
One issue remaining: You've committed MIGRATION_V7.md.bak — this is a backup file that shouldn't be in the repo. Please remove it:
git rm MIGRATION_V7.md.bak
git commit -m 'chore: remove backup file'
git push
Once that's removed, I'll approve.
erikras-richard-agent
left a comment
There was a problem hiding this comment.
LGTM! .bak file removed, form-level initialValues check added correctly. Clean fix for StrictMode + destroyOnUnregister. ✅
TypeScript was correctly flagging that formState.initialValues could be undefined. Added checks before calling getIn to avoid type errors.
ESLint requires unused variables to match /^_/u pattern.
Per Erik's feedback - no need to keep it even with underscore prefix.
Summary
Fixes #1031
Ensures initial values are correctly applied when using
destroyOnUnregisterwith React 18 StrictMode.Problem
When using
destroyOnUnregisterwith React 18 StrictMode, fields would display empty instead of showing their initial values.Root Cause
React 18 StrictMode deliberately mounts components twice to help detect side effects:
destroyOnUnregisterremoves it from form stateResult: Empty fields despite having initial values.
Workarounds (before this fix)
Users had to:
destroyOnUnregisterinitialValuesEqualwith() => falseNone of these are ideal.
Solution
Before registering a field, check if it doesn't exist in form state (indicating it was destroyed). If destroyed and we have an initial value, explicitly call
form.change()to set the value before registering.This ensures initial values are always reapplied when a field re-registers after being destroyed by
destroyOnUnregister.Impact
✅ Fixes the bug
destroyOnUnregister✅ No breaking changes
✅ Handles all cases
initialValuepropinitialValuesprop"user.name")Testing
Related
This is a common React 18 StrictMode issue pattern. Similar fixes have been applied to other form libraries.
Fixes #1031
Summary by CodeRabbit