Skip to content

Commit 92ef7e1

Browse files
LeCarbonatorautofix-ci[bot]lachlancollins
authored
fix(form-core): fix fields being stale on array changes (#1729)
* add 'found' property to getBy * WIP: for testing * ci: apply automated fixes and generate docs * refactor metaHelper for clarity * add baseStore to fieldApi * chore: remove dead code from metaHelper * chore: let's see what sticks * chore: remove unneeded additional store * fix undefined errors being passed to fieldMeta * remove unused import * chore: add tests for vue * chore: clean up previous drafts * remove unrelated unit test diff * fix: prevent defaultValue regression * chore: adjust unit tests's target value * fix: adhere to rules of react for useField * chore: add solidjs unit test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com>
1 parent 75be351 commit 92ef7e1

File tree

10 files changed

+430
-164
lines changed

10 files changed

+430
-164
lines changed

packages/form-core/src/FieldApi.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import { defaultFieldMeta } from './metaHelper'
77
import {
88
determineFieldLevelErrorSourceAndValue,
9+
evaluate,
910
getAsyncValidatorArray,
1011
getBy,
1112
getSyncValidatorArray,
@@ -1052,7 +1053,7 @@ export class FieldApi<
10521053
/**
10531054
* The field name.
10541055
*/
1055-
name!: DeepKeys<TParentData>
1056+
name: TName
10561057
/**
10571058
* The field options.
10581059
*/
@@ -1151,8 +1152,10 @@ export class FieldApi<
11511152
TParentSubmitMeta
11521153
>,
11531154
) {
1154-
this.form = opts.form as never
1155-
this.name = opts.name as never
1155+
this.form = opts.form
1156+
this.name = opts.name
1157+
this.options = opts
1158+
11561159
this.timeoutIds = {
11571160
validations: {} as Record<ValidationCause, never>,
11581161
listeners: {} as Record<ListenerCause, never>,
@@ -1162,12 +1165,21 @@ export class FieldApi<
11621165
this.store = new Derived({
11631166
deps: [this.form.store],
11641167
fn: () => {
1165-
const value = this.form.getFieldValue(this.name)
11661168
const meta = this.form.getFieldMeta(this.name) ?? {
11671169
...defaultFieldMeta,
11681170
...opts.defaultMeta,
11691171
}
11701172

1173+
let value = this.form.getFieldValue(this.name)
1174+
if (
1175+
!meta.isTouched &&
1176+
(value as unknown) === undefined &&
1177+
this.options.defaultValue !== undefined &&
1178+
!evaluate(value, this.options.defaultValue)
1179+
) {
1180+
value = this.options.defaultValue
1181+
}
1182+
11711183
return {
11721184
value,
11731185
meta,
@@ -1196,8 +1208,6 @@ export class FieldApi<
11961208
>
11971209
},
11981210
})
1199-
1200-
this.options = opts as never
12011211
}
12021212

12031213
/**
@@ -1232,8 +1242,8 @@ export class FieldApi<
12321242
mount = () => {
12331243
const cleanup = this.store.mount()
12341244

1235-
if ((this.options.defaultValue as unknown) !== undefined) {
1236-
this.form.setFieldValue(this.name, this.options.defaultValue as never, {
1245+
if (this.options.defaultValue !== undefined && !this.getMeta().isTouched) {
1246+
this.form.setFieldValue(this.name, this.options.defaultValue, {
12371247
dontUpdateMeta: true,
12381248
})
12391249
}
@@ -1309,33 +1319,23 @@ export class FieldApi<
13091319
TParentSubmitMeta
13101320
>,
13111321
) => {
1312-
this.options = opts as never
1313-
1314-
const nameHasChanged = this.name !== opts.name
1322+
this.options = opts
13151323
this.name = opts.name
13161324

13171325
// Default Value
1318-
if ((this.state.value as unknown) === undefined) {
1319-
const formDefault = getBy(opts.form.options.defaultValues, opts.name)
1320-
1321-
const defaultValue = (opts.defaultValue as unknown) ?? formDefault
1322-
1323-
// The name is dynamic in array fields. It changes when the user performs operations like removing or reordering.
1324-
// In this case, we don't want to force a default value if the store managed to find an existing value.
1325-
if (nameHasChanged) {
1326-
this.setValue((val) => (val as unknown) || defaultValue, {
1327-
dontUpdateMeta: true,
1328-
})
1329-
} else if (defaultValue !== undefined) {
1330-
this.setValue(defaultValue as never, {
1326+
if (!this.state.meta.isTouched && this.options.defaultValue !== undefined) {
1327+
const formField = this.form.getFieldValue(this.name)
1328+
if (!evaluate(formField, opts.defaultValue)) {
1329+
this.form.setFieldValue(this.name, opts.defaultValue as never, {
13311330
dontUpdateMeta: true,
1331+
dontValidate: true,
1332+
dontRunListeners: true,
13321333
})
13331334
}
13341335
}
13351336

1336-
// Default Meta
1337-
if (this.form.getFieldMeta(this.name) === undefined) {
1338-
this.setMeta(this.state.meta)
1337+
if (!this.form.getFieldMeta(this.name)) {
1338+
this.form.setFieldMeta(this.name, this.state.meta)
13391339
}
13401340
}
13411341

packages/form-core/src/FormApi.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,20 +1071,18 @@ export class FormApi<
10711071
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
10721072
fieldErrors = Object.values(currBaseMeta.errorMap ?? {}).filter(
10731073
(val) => val !== undefined,
1074-
) as never
1074+
)
10751075

10761076
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
10771077
const fieldInstance = this.getFieldInfo(fieldName)?.instance
10781078

10791079
if (fieldInstance && !fieldInstance.options.disableErrorFlat) {
1080-
fieldErrors = (fieldErrors as undefined | string[])?.flat(
1081-
1,
1082-
) as never
1080+
fieldErrors = fieldErrors.flat(1)
10831081
}
10841082
}
10851083

10861084
// As primitives, we don't need to aggressively persist the same referential value for performance reasons
1087-
const isFieldValid = !isNonEmptyArray(fieldErrors ?? [])
1085+
const isFieldValid = !isNonEmptyArray(fieldErrors)
10881086
const isFieldPristine = !currBaseMeta.isDirty
10891087
const isDefaultValue =
10901088
evaluate(
@@ -1112,11 +1110,11 @@ export class FormApi<
11121110

11131111
fieldMeta[fieldName] = {
11141112
...currBaseMeta,
1115-
errors: fieldErrors,
1113+
errors: fieldErrors ?? [],
11161114
isPristine: isFieldPristine,
11171115
isValid: isFieldValid,
11181116
isDefaultValue: isDefaultValue,
1119-
} as AnyFieldMeta
1117+
} satisfies AnyFieldMeta as AnyFieldMeta
11201118
}
11211119

11221120
if (!Object.keys(currBaseStore.fieldMetaBase).length) return fieldMeta
@@ -2347,7 +2345,7 @@ export class FormApi<
23472345
}
23482346

23492347
// Shift down all meta after validating to make sure the new field has been mounted
2350-
metaHelper(this).handleArrayFieldMetaShift(field, index, 'insert')
2348+
metaHelper(this).handleArrayInsert(field, index)
23512349

23522350
if (!dontValidate) {
23532351
await this.validateArrayFieldsStartingFrom(field, index, 'change')
@@ -2408,7 +2406,7 @@ export class FormApi<
24082406
)
24092407

24102408
// Shift up all meta
2411-
metaHelper(this).handleArrayFieldMetaShift(field, index, 'remove')
2409+
metaHelper(this).handleArrayRemove(field, index)
24122410

24132411
if (lastIndex !== null) {
24142412
const start = `${field}[${lastIndex}]`
@@ -2443,7 +2441,7 @@ export class FormApi<
24432441
)
24442442

24452443
// Swap meta
2446-
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'swap', index2)
2444+
metaHelper(this).handleArraySwap(field, index1, index2)
24472445

24482446
const dontValidate = options?.dontValidate ?? false
24492447
if (!dontValidate) {
@@ -2475,7 +2473,7 @@ export class FormApi<
24752473
)
24762474

24772475
// Move meta between index1 and index2
2478-
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'move', index2)
2476+
metaHelper(this).handleArrayMove(field, index1, index2)
24792477

24802478
const dontValidate = options?.dontValidate ?? false
24812479
if (!dontValidate) {

0 commit comments

Comments
 (0)