useField v7.0.1 overwrites form values set via change() for previously unregistered field paths
Bug Report
Version
react-final-form: 7.0.1final-form: 5.0.1react: 19.0.6
Description
The StrictMode fix added in v7.0.1 (PR #1068/#1069, fixing #1031) introduced a regression where useField overwrites existing form values when a field is registered at a path that was never previously registered as its own field — even when destroyOnUnregister is false (the default).
This breaks multi-step/wizard forms where different steps register useField at different paths within the same form state tree.
Reproduction
- Step 1 component registers
useField("parent")and callsform.change("parent.child.value", data)to set a nested value - User advances to step 2 — step 1 unmounts, step 2 mounts
- Step 2 component registers
useField("parent.child.value")to read the value - Expected:
useFieldreturnsdata(the value set in step 1) - Actual:
useFieldoverwrites it withinitialValues.parent.child.value, discardingdata
Root Cause
The fix added in v7.0.1 prepends this logic to useField's useEffect:
var existingFieldState = form.getFieldState(name);
if (!existingFieldState) {
var formInitialValue = getIn(formState.initialValues, name);
var valueToSet = formInitialValue !== undefined ? formInitialValue : initialValue;
if (valueToSet !== undefined) {
form.change(name, valueToSet);
}
}The problem is that getFieldState(name) checks whether a field is registered at that exact path, not whether a value exists in the form state at that path. When a value was set via change() through a parent field (or directly on the form), no field registration exists at the nested path, so getFieldState returns null, and the code overwrites the existing value with initialValues.
This code was not present in v7.0.0 — in that version, useEffect simply called register() without the getFieldState check.
Suggested Fix
Before calling form.change(), check whether the form's current values already differ from initialValues at that path:
if (!existingFieldState) {
var formState = form.getState();
var currentValue = getIn(formState.values, name);
var formInitialValue = formState.initialValues ? getIn(formState.initialValues, name) : undefined;
var valueToSet = formInitialValue !== undefined ? formInitialValue : initialValue;
// Only reset if no value has been set via change()
if (valueToSet !== undefined && currentValue === undefined) {
form.change(name, valueToSet);
}
}Workaround
Set the initial value for affected fields to undefined instead of null in initialValues. This causes valueToSet to be undefined, which skips the form.change() call entirely:
// Before (broken): initialValues triggers overwrite
{ Data: null }
// After (workaround): undefined skips the form.change() call
{ Data: undefined }Source: final-form/react-final-form