diff --git a/src/FormController.js b/src/FormController.js index 955bda3b..115319ff 100644 --- a/src/FormController.js +++ b/src/FormController.js @@ -899,26 +899,26 @@ export class FormController { if (!keepValue) { debug('Delete Value', name); - ObjectMap.delete(this.state.values, name); + ObjectMap.unset(this.state.values, name); debug('Delete Modified', name); - ObjectMap.delete(this.state.modified, name); + ObjectMap.unset(this.state.modified, name); debug('Delete Masked', name); - ObjectMap.delete(this.state.maskedValues, name); + ObjectMap.unset(this.state.maskedValues, name); } if (!keepTouched) { debug('Delete Touched', name); - ObjectMap.delete(this.state.touched, name); + ObjectMap.unset(this.state.touched, name); } if (!keepError) { debug('Delete Errors', name); - ObjectMap.delete(this.state.errors, name); + ObjectMap.unset(this.state.errors, name); } debug('Delete Dirt', name); - ObjectMap.delete(this.state.dirt, name); + ObjectMap.unset(this.state.dirt, name); debug('Delete Focused', name); - ObjectMap.delete(this.state.focused, name); + ObjectMap.unset(this.state.focused, name); debug('Delete Info', name); - ObjectMap.delete(this.state.data, name); + ObjectMap.unset(this.state.data, name); // Remember to update valid this.updateValid(); diff --git a/src/ObjectMap.js b/src/ObjectMap.js index a2616545..27f1a37e 100644 --- a/src/ObjectMap.js +++ b/src/ObjectMap.js @@ -262,6 +262,18 @@ export class ObjectMap { } } + // Like delete but never splices arrays -- just unsets the key. + // Used by FormController.remove() so that sibling field indices stay stable. + static unset(object, path) { + debug('UNSET', path); + ldunset(object, path); + + let pathArray = ldtoPath(path); + pathArray = pathArray.slice(0, pathArray.length - 1); + cleanup(object, pathArray); + debug('UNSET DONE', path); + } + static delete(object, path) { debug('DELETE', path); diff --git a/src/hooks/useField.js b/src/hooks/useField.js index cfe2be32..5276fb1f 100644 --- a/src/hooks/useField.js +++ b/src/hooks/useField.js @@ -5,6 +5,7 @@ import { useFieldState } from './useFieldState'; import { useFormController } from './useFormController'; import { useCursorPosition } from './useCursorPosition'; import { + ArrayFieldItemStateContext, MultistepStepContext, RelevanceContext, ScopeContext @@ -154,6 +155,9 @@ export const useField = ({ // For multistep const inMultistep = useContext(MultistepStepContext); + // For array field detection -- fields inside ArrayField have index-managed values + const inArrayField = useContext(ArrayFieldItemStateContext); + // For relevance const isRelevant = useRelevance({ name, @@ -351,17 +355,24 @@ export const useField = ({ [...formatterDependencies] ); - // Note im not adding this yet as I need to figure out how to solve issue with array fields when you remove 1 [0, 1, 2] and 2 becomes 1 - // useUpdateEffect( - // () => { - // // If the form is pristine then reset it when we get new initial values ! - // const pristine = fieldApi.getPristine(); - // if (pristine) { - // fieldApi.reset(); - // } - // }, - // [userInitialValue, defaultValue] - // ); + // Reset field when defaultValue/initialValue changes and form is still pristine. + // Previously disabled because remove() spliced arrays, shifting indices. + // Now safe because remove() uses ObjectMap.unset() which preserves indices. + // Skip for fields inside ArrayField -- their values are managed by the + // ArrayField's own index-shifting logic, not by defaultValue props. + useUpdateEffect( + () => { + if (inArrayField) { + return; + } + + const pristine = fieldApi.getPristine(); + if (pristine) { + fieldApi.reset(); + } + }, + [userInitialValue, defaultValue, inArrayField] + ); useFieldSubscription( 'field-value',