Skip to content

Commit 0f1b4c3

Browse files
committed
chore: updated to handel non-primitives
1 parent 6797693 commit 0f1b4c3

File tree

4 files changed

+148
-16
lines changed

4 files changed

+148
-16
lines changed

packages/form-core/src/FormApi.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Derived, Store, batch } from '@tanstack/store'
22
import {
3+
deepEqual,
34
deleteBy,
45
determineFormLevelErrorSourceAndValue,
56
functionalUpdate,
@@ -924,9 +925,10 @@ export class FormApi<
924925
// As primitives, we don't need to aggressively persist the same referential value for performance reasons
925926
const isFieldValid = !isNonEmptyArray(fieldErrors ?? [])
926927
const isFieldPristine = !currBaseMeta.isDirty
927-
const isDefaultValue =
928-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
929-
curFieldVal === this.options.defaultValues?.[fieldName as never]
928+
const isDefaultValue = deepEqual(
929+
curFieldVal,
930+
this.options.defaultValues?.[fieldName as never],
931+
)
930932

931933
if (
932934
prevFieldInfo &&

packages/form-core/src/utils.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
ValidationSource,
66
} from './types'
77
import type { FormValidators } from './FormApi'
8-
import type { AnyFieldMeta, FieldValidators } from './FieldApi'
8+
import type { FieldValidators } from './FieldApi'
99

1010
export type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput
1111

@@ -443,3 +443,37 @@ export const determineFieldLevelErrorSourceAndValue = ({
443443

444444
return { newErrorValue: undefined, newSource: undefined }
445445
}
446+
447+
export const deepEqual = <T>(a: T, b: T): boolean => {
448+
// returns primitive equality
449+
if (a === b) {
450+
return true
451+
}
452+
453+
// checks that props are object and not null
454+
if (
455+
typeof a !== 'object' ||
456+
a === null ||
457+
typeof b !== 'object' ||
458+
b === null
459+
) {
460+
return false
461+
}
462+
463+
// arrays are treated as objects with numeric keys
464+
const keysA = Object.keys(a)
465+
const keysB = Object.keys(b)
466+
467+
if (keysA.length !== keysB.length) {
468+
return false
469+
}
470+
471+
// recursively calls deepEqual down the object chain
472+
for (const key of keysA) {
473+
if (!keysB.includes(key) || !deepEqual((a as any)[key], (b as any)[key])) {
474+
return false
475+
}
476+
}
477+
478+
return true
479+
}

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

+57-12
Original file line numberDiff line numberDiff line change
@@ -100,29 +100,74 @@ describe('field api', () => {
100100
})
101101

102102
it('should update the fields meta isDefaultValue', () => {
103-
const form = new FormApi({
103+
// primitives
104+
const primitiveMetaForm = new FormApi({
104105
defaultValues: {
105106
name: 'test',
106107
},
107108
})
108-
form.mount()
109+
primitiveMetaForm.mount()
109110

110-
const field = new FieldApi({
111-
form,
111+
const primitiveField = new FieldApi({
112+
form: primitiveMetaForm,
112113
name: 'name',
113114
})
114115

115-
field.mount()
116-
expect(field.getMeta().isDefaultValue).toBe(true)
116+
primitiveField.mount()
117+
expect(primitiveField.getMeta().isDefaultValue).toBe(true)
117118

118-
field.setValue('not-test')
119-
expect(field.getMeta().isDefaultValue).toBe(false)
119+
primitiveField.setValue('not-test')
120+
expect(primitiveField.getMeta().isDefaultValue).toBe(false)
120121

121-
field.setValue('test')
122-
expect(field.getMeta().isDefaultValue).toBe(true)
122+
primitiveField.setValue('test')
123+
expect(primitiveField.getMeta().isDefaultValue).toBe(true)
124+
125+
primitiveMetaForm.resetField('name')
126+
expect(primitiveField.getMeta().isDefaultValue).toBe(true)
127+
128+
// arrays
129+
const arrayMetaForm = new FormApi({
130+
defaultValues: {
131+
arr: ['', ''],
132+
},
133+
})
134+
arrayMetaForm.mount()
135+
136+
const arrayField = new FieldApi({
137+
form: arrayMetaForm,
138+
name: 'arr',
139+
})
140+
141+
arrayField.mount()
142+
expect(arrayField.getMeta().isDefaultValue).toBe(true)
143+
144+
arrayField.setValue(['hello', 'goodbye'])
145+
expect(arrayField.getMeta().isDefaultValue).toBe(false)
146+
147+
arrayField.setValue(['', ''])
148+
expect(arrayField.getMeta().isDefaultValue).toBe(true)
149+
150+
// objects
151+
const objectMetaForm = new FormApi({
152+
defaultValues: {
153+
obj: { firstName: 'John', lastName: 'Wick' },
154+
},
155+
})
156+
objectMetaForm.mount()
157+
158+
const objectField = new FieldApi({
159+
form: objectMetaForm,
160+
name: 'obj',
161+
})
162+
163+
objectField.mount()
164+
expect(objectField.getMeta().isDefaultValue).toBe(true)
165+
166+
objectField.setValue({ firstName: 'John', lastName: 'Travolta' })
167+
expect(objectField.getMeta().isDefaultValue).toBe(false)
123168

124-
form.resetField('name')
125-
expect(field.getMeta().isDefaultValue).toBe(true)
169+
objectField.setValue({ firstName: 'John', lastName: 'Wick' })
170+
expect(objectField.getMeta().isDefaultValue).toBe(true)
126171
})
127172

128173
it('should set a value correctly', () => {

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

+51
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, it } from 'vitest'
22
import {
3+
deepEqual,
34
deleteBy,
45
determineFieldLevelErrorSourceAndValue,
56
determineFormLevelErrorSourceAndValue,
@@ -492,3 +493,53 @@ describe('determineFieldLevelErrorSourceAndValue', () => {
492493
})
493494
})
494495
})
496+
497+
describe('deepEqual', () => {
498+
it('should test equality between primitives', () => {
499+
const numbersTrue = deepEqual(1, 1)
500+
expect(numbersTrue).toEqual(true)
501+
502+
const stringFalse = deepEqual('uh oh', '')
503+
expect(stringFalse).toEqual(false)
504+
505+
const boolTrue = deepEqual(true, true)
506+
expect(boolTrue).toEqual(true)
507+
508+
const nullFalse = deepEqual(null, {})
509+
expect(nullFalse).toEqual(false)
510+
511+
const undefinedFalse = deepEqual(undefined, null)
512+
expect(undefinedFalse).toEqual(false)
513+
})
514+
515+
it('should test equality between arrays', () => {
516+
const arrayTrue = deepEqual([], [])
517+
expect(arrayTrue).toEqual(true)
518+
519+
const arrayDeepTrue = deepEqual([[1]], [[1]])
520+
expect(arrayDeepTrue).toEqual(true)
521+
522+
const arrayFalse = deepEqual([], [''])
523+
expect(arrayFalse).toEqual(false)
524+
525+
const arrayDeepFalse = deepEqual([[1]], [])
526+
expect(arrayDeepFalse).toEqual(false)
527+
})
528+
529+
it('should test equality between objects', () => {
530+
const objTrue = deepEqual({ test: 'same' }, { test: 'same' })
531+
expect(objTrue).toEqual(true)
532+
533+
const objFalse = deepEqual({ test: 'not' }, { test: 'same' })
534+
expect(objFalse).toEqual(false)
535+
536+
const objDeepFalse = deepEqual({ test: 'not' }, { test: { test: 'same' } })
537+
expect(objDeepFalse).toEqual(false)
538+
539+
const objDeepArrFalse = deepEqual({ test: [] }, { test: [[]] })
540+
expect(objDeepArrFalse).toEqual(false)
541+
542+
const objNullFalse = deepEqual({ test: '' }, null)
543+
expect(objNullFalse).toEqual(false)
544+
})
545+
})

0 commit comments

Comments
 (0)