Skip to content

Commit b37f72f

Browse files
committed
feat(form-core): field meta isDefaultValue
1 parent 2d67af3 commit b37f72f

File tree

4 files changed

+69
-14
lines changed

4 files changed

+69
-14
lines changed

packages/form-core/src/FieldApi.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,10 @@ export type FieldMetaDerived<
656656
* A boolean indicating if the field is valid. Evaluates `true` if there are no field errors.
657657
*/
658658
isValid: boolean
659+
/**
660+
* A flag indicating whether the field's current value is the default value
661+
*/
662+
isDefaultValue: boolean
659663
}
660664

661665
export type AnyFieldMetaDerived = FieldMetaDerived<

packages/form-core/src/FormApi.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,10 @@ export type DerivedFormState<
626626
* A boolean indicating if none of the form's fields' values have been modified by the user. Evaluates `true` if the user have not modified any of the fields. Opposite of `isDirty`.
627627
*/
628628
isPristine: boolean
629+
/**
630+
* A boolean indicating if all of the form's fields are the same as default values.
631+
*/
632+
isDefaultValue: boolean
629633
/**
630634
* A boolean indicating if the form and all its fields are valid. Evaluates `true` if there are no errors.
631635
*/
@@ -857,6 +861,7 @@ export class FormApi<
857861
}),
858862
)
859863

864+
// creates a tanstack store derived record of the fields derivedMeta that lazily updates
860865
this.fieldMetaDerived = new Derived({
861866
deps: [this.baseStore],
862867
fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
@@ -883,21 +888,30 @@ export class FormApi<
883888
for (const fieldName of Object.keys(
884889
currBaseStore.fieldMetaBase,
885890
) as Array<keyof typeof currBaseStore.fieldMetaBase>) {
886-
const currBaseVal = currBaseStore.fieldMetaBase[
891+
// an object representing the current fieldMeta
892+
const currBaseMeta = currBaseStore.fieldMetaBase[
887893
fieldName as never
888894
] as AnyFieldMetaBase
889895

890-
const prevBaseVal = prevBaseStore?.fieldMetaBase[
896+
// an object representing the previous fieldMeta
897+
const prevBaseMeta = prevBaseStore?.fieldMetaBase[
891898
fieldName as never
892899
] as AnyFieldMetaBase | undefined
893900

901+
// an object representing the current fieldInfo
894902
const prevFieldInfo =
895903
prevVal?.[fieldName as never as keyof typeof prevVal]
896904

905+
// current field value
906+
const curFieldVal = currBaseStore.values[fieldName as never]
907+
897908
let fieldErrors = prevFieldInfo?.errors
898-
if (!prevBaseVal || currBaseVal.errorMap !== prevBaseVal.errorMap) {
909+
if (
910+
!prevBaseMeta ||
911+
currBaseMeta.errorMap !== prevBaseMeta.errorMap
912+
) {
899913
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
900-
fieldErrors = Object.values(currBaseVal.errorMap ?? {}).filter(
914+
fieldErrors = Object.values(currBaseMeta.errorMap ?? {}).filter(
901915
(val) => val !== undefined,
902916
) as never
903917

@@ -912,26 +926,31 @@ export class FormApi<
912926
}
913927

914928
// As primitives, we don't need to aggressively persist the same referential value for performance reasons
915-
const isFieldPristine = !currBaseVal.isDirty
916929
const isFieldValid = !isNonEmptyArray(fieldErrors ?? [])
930+
const isFieldPristine = !currBaseMeta.isDirty
931+
const isDefaultValue =
932+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
933+
curFieldVal === this.options.defaultValues?.[fieldName as never]
917934

918935
if (
919936
prevFieldInfo &&
920937
prevFieldInfo.isPristine === isFieldPristine &&
921938
prevFieldInfo.isValid === isFieldValid &&
939+
prevFieldInfo.isDefaultValue === isDefaultValue &&
922940
prevFieldInfo.errors === fieldErrors &&
923-
currBaseVal === prevBaseVal
941+
currBaseMeta === prevBaseMeta
924942
) {
925943
fieldMeta[fieldName] = prevFieldInfo
926944
originalMetaCount++
927945
continue
928946
}
929947

930948
fieldMeta[fieldName] = {
931-
...currBaseVal,
949+
...currBaseMeta,
932950
errors: fieldErrors,
933951
isPristine: isFieldPristine,
934952
isValid: isFieldValid,
953+
isDefaultValue: isDefaultValue,
935954
} as AnyFieldMeta
936955
}
937956

@@ -981,6 +1000,9 @@ export class FormApi<
9811000

9821001
const isTouched = fieldMetaValues.some((field) => field.isTouched)
9831002
const isBlurred = fieldMetaValues.some((field) => field.isBlurred)
1003+
const isDefaultValue = fieldMetaValues.some(
1004+
(field) => field.isDefaultValue,
1005+
)
9841006

9851007
const shouldInvalidateOnMount =
9861008
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -1059,6 +1081,7 @@ export class FormApi<
10591081
prevVal.isTouched === isTouched &&
10601082
prevVal.isBlurred === isBlurred &&
10611083
prevVal.isPristine === isPristine &&
1084+
prevVal.isDefaultValue === isDefaultValue &&
10621085
prevVal.isDirty === isDirty &&
10631086
shallow(prevBaseStore, currBaseStore)
10641087
) {
@@ -1078,6 +1101,7 @@ export class FormApi<
10781101
isTouched,
10791102
isBlurred,
10801103
isPristine,
1104+
isDefaultValue,
10811105
isDirty,
10821106
} as FormState<
10831107
TFormData,

packages/form-core/src/metaHelper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const defaultFieldMeta: AnyFieldMeta = {
1515
isDirty: false,
1616
isPristine: true,
1717
isValid: true,
18+
isDefaultValue: true,
1819
errors: [],
1920
errorMap: {},
2021
errorSourceMap: {},

packages/form-core/tests/FieldApi.spec.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ describe('field api', () => {
5757
expect(field.getMeta()).toEqual({
5858
isTouched: false,
5959
isBlurred: false,
60+
isDefaultValue: true,
6061
isValidating: false,
6162
isPristine: true,
6263
isValid: true,
@@ -68,7 +69,7 @@ describe('field api', () => {
6869
})
6970

7071
it('should allow to set default meta', () => {
71-
const form = new FormApi()
72+
const form = new FormApi({ defaultValues: { name: '' } })
7273

7374
form.mount()
7475

@@ -77,27 +78,52 @@ describe('field api', () => {
7778
name: 'name',
7879
defaultMeta: {
7980
isTouched: true,
80-
isDirty: true,
81-
isPristine: false,
8281
isBlurred: true,
82+
isDirty: true,
8383
},
8484
})
8585

8686
field.mount()
8787

88-
expect(field.getMeta()).toEqual({
88+
expect(field.getMeta()).toMatchObject({
8989
isTouched: true,
9090
isBlurred: true,
91-
isValidating: false,
9291
isDirty: true,
93-
isPristine: false,
92+
9493
isValid: true,
94+
isValidating: false,
9595
errors: [],
9696
errorMap: {},
9797
errorSourceMap: {},
9898
})
9999
})
100100

101+
it('should update the fields meta isDefaultValue', () => {
102+
const form = new FormApi({
103+
defaultValues: {
104+
name: 'test',
105+
},
106+
})
107+
form.mount()
108+
109+
const field = new FieldApi({
110+
form,
111+
name: 'name',
112+
})
113+
114+
field.mount()
115+
expect(field.getMeta().isDefaultValue).toBe(true)
116+
117+
field.setValue('not-test')
118+
expect(field.getMeta().isDefaultValue).toBe(false)
119+
120+
field.setValue('test')
121+
expect(field.getMeta().isDefaultValue).toBe(true)
122+
123+
form.resetField('name')
124+
expect(field.getMeta().isDefaultValue).toBe(true)
125+
})
126+
101127
it('should set a value correctly', () => {
102128
const form = new FormApi({
103129
defaultValues: {
@@ -560,7 +586,7 @@ describe('field api', () => {
560586

561587
field.moveValue(2, 0)
562588

563-
expect(field.getValue()).toStrictEqual(['three', 'one', 'two', 'four'])
589+
expect(field.state.value).toStrictEqual(['three', 'one', 'two', 'four'])
564590
})
565591

566592
it('should run onChange validation when moving an array fields value', () => {

0 commit comments

Comments
 (0)