Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-fields-reset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/form-core': patch
---

Fix `form.resetField()` leaving the current value in place when the field's default value is `undefined`. The field is now reset to `undefined`, matching `form.reset()` and `isDefaultValue`. A field-level `defaultValue` of `null` is also restored instead of falling through to the form-level default.
12 changes: 7 additions & 5 deletions packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2940,18 +2940,20 @@ export class FormApi<
const fieldDefault =
this.getFieldInfo(field).instance?.options.defaultValue
const formDefault = getBy(this.options.defaultValues, field)
const targetValue = fieldDefault ?? formDefault
const targetValue =
fieldDefault !== undefined ? fieldDefault : formDefault
const hasDefault =
fieldDefault !== undefined || this.options.defaultValues !== undefined
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return {
...prev,
fieldMetaBase: {
...prev.fieldMetaBase,
[field]: defaultFieldMeta,
},
values:
targetValue !== undefined
? setBy(prev.values, field, targetValue)
: prev.values,
values: hasDefault
? setBy(prev.values, field, targetValue)
: prev.values,
}
})
}
Expand Down
129 changes: 129 additions & 0 deletions packages/form-core/tests/FormApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4236,6 +4236,135 @@ it('should preserve nested fields on resetField if defaultValues is not provided
expect(form.state.values.nested.field.name).toEqual('Nested')
})

it.each([
['undefined', undefined],
['null', null],
['an empty string', ''],
['false', false],
['0', 0],
['a defined value', 'tony'],
])(
'should reset a field to a form-level default of %s on resetField',
(_, defaultValue) => {
const form = new FormApi({
defaultValues: { name: defaultValue } as { name: unknown },
})
form.mount()

const field = new FieldApi({ form, name: 'name' })
field.mount()

field.setValue('hawk')
expect(field.getMeta().isDefaultValue).toBe(false)

form.resetField('name')

expect(form.state.values.name).toBe(defaultValue)
expect(field.state.value).toBe(defaultValue)
expect(field.getMeta().isDefaultValue).toBe(true)
expect(field.getMeta().isTouched).toBe(false)
},
)

it('should reset a field to undefined on resetField if its key is missing from defaultValues', () => {
const form = new FormApi({
defaultValues: {} as { name?: string },
})
form.mount()

const field = new FieldApi({ form, name: 'name' })
field.mount()

field.setValue('hawk')
form.resetField('name')

expect(form.state.values.name).toBeUndefined()
expect(field.getMeta().isDefaultValue).toBe(true)
})

it('should reset nested fields with undefined defaults on resetField without changing their siblings', () => {
const form = new FormApi({
defaultValues: {
nested: { name: undefined, age: 1 },
people: [{ name: undefined }, { name: 'second' }],
} as {
nested: { name?: string; age: number }
people: { name?: string }[]
},
})
form.mount()

form.setFieldValue('nested.name', 'Nested')
form.setFieldValue('nested.age', 2)
form.setFieldValue('people[0].name', 'first')
form.setFieldValue('people[1].name', 'changed')

form.resetField('nested.name')
form.resetField('people[0].name')

expect(form.state.values).toEqual({
nested: { name: undefined, age: 2 },
people: [{ name: undefined }, { name: 'changed' }],
})
})

it.each([
['a defined value', 'tony'],
['an empty string', ''],
['false', false],
['0', 0],
])(
'should reset a field to a field-level default of %s on resetField if defaultValues is not provided',
(_, defaultValue) => {
const form = new FormApi({
defaultState: { values: { name: 'initial' } as { name: unknown } },
})
form.mount()

const field = new FieldApi({ form, name: 'name', defaultValue })
field.mount()

field.setValue('hawk')
form.resetField('name')

expect(form.state.values.name).toBe(defaultValue)
expect(field.getMeta().isDefaultValue).toBe(true)
},
)

it('should reset a field to a field-level default of null on resetField if defaultValues is not provided', () => {
const form = new FormApi({
defaultState: { values: { name: 'initial' } as { name: unknown } },
})
form.mount()

const field = new FieldApi({ form, name: 'name', defaultValue: null })
field.mount()

field.setValue('hawk')
form.resetField('name')

expect(form.state.values.name).toBeNull()
expect(field.state.value).toBeNull()
expect(field.getMeta().isTouched).toBe(false)
})

it('should prefer a field-level default of null over the form-level default on resetField', () => {
const form = new FormApi({
defaultValues: { name: 'tony' } as { name: unknown },
})
form.mount()

const field = new FieldApi({ form, name: 'name', defaultValue: null })
field.mount()

field.setValue('hawk')
form.resetField('name')

expect(form.state.values.name).toBeNull()
expect(field.state.value).toBeNull()
})

it('should accept formId and return it', () => {
const form = new FormApi({
defaultValues: { age: 0 },
Expand Down