Skip to content

Commit

Permalink
feat: add preserve value to field API (#444)
Browse files Browse the repository at this point in the history
* feat: add preserve value to field API

* removed conflict

* add test for core/fieldAPI

* add test for react/fieldAPI

* chore: fix TS usage and format

---------

Co-authored-by: Corbin Crutchley <[email protected]>
  • Loading branch information
vikaskumar89 and crutchcorn authored Oct 30, 2023
1 parent aefe7e4 commit 57bc462
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 4 deletions.
11 changes: 8 additions & 3 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export interface FieldOptions<
defaultValue?: TData
asyncDebounceMs?: number
asyncAlways?: boolean
preserveValue?: boolean
validator?: ValidatorType
onMount?: (
formApi: FieldApi<TParentData, TName, ValidatorType, TData>,
Expand Down Expand Up @@ -239,10 +240,14 @@ export class FieldApi<
this.options.onMount?.(this as never)

return () => {
const preserveValue = this.options.preserveValue
unsubscribe()
delete info.instances[this.uid]
if (!Object.keys(info.instances).length) {
delete this.form.fieldInfo[this.name as never]
if (!preserveValue) {
delete info.instances[this.uid]
}

if (!Object.keys(info.instances).length && !preserveValue) {
delete this.form.fieldInfo[this.name]
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ export class FormApi<TFormData, ValidatorType> {
})
}

deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {
delete this.state.values[field as keyof TFormData]
delete this.state.fieldMeta[field]
}

pushFieldValue = <TField extends DeepKeys<TFormData>>(
field: TField,
value: DeepValue<TFormData, TField> extends any[]
Expand Down
39 changes: 39 additions & 0 deletions packages/form-core/src/tests/FieldApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,43 @@ describe('field api', () => {

expect(field.state.value).toBe('test')
})

// test the unmounting of the fieldAPI
it('should preserve value on unmount', () => {
const form = new FormApi({
defaultValues: {
name: 'test',
},
})

const field = new FieldApi({
form,
name: 'name',
preserveValue: true,
})

const unmount = field.mount()
unmount()
expect(form.getFieldInfo(field.name).instances[field.uid]).toBeDefined()
expect(form.getFieldInfo(field.name)).toBeDefined()
})

it('should not preserve field value on ummount', () => {
const form = new FormApi({
defaultValues: {
name: 'test',
},
})

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

const unmount = field.mount()
unmount()
const info = form.getFieldInfo(field.name)
expect(info.instances[field.uid]).toBeUndefined()
expect(Object.keys(info.instances).length).toBe(0)
})
})
15 changes: 15 additions & 0 deletions packages/form-core/src/tests/FormApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,21 @@ describe('form api', () => {
expect(form.getFieldValue('name')).toEqual('two')
})

it('should delete field from the form', () => {
const form = new FormApi({
defaultValues: {
names: 'kittu',
age: 4,
},
})

form.deleteField('names')

expect(form.getFieldValue('age')).toStrictEqual(4)
expect(form.getFieldValue('names')).toStrictEqual(undefined)
expect(form.getFieldMeta('names')).toStrictEqual(undefined)
})

it("form's valid state should be work fine", () => {
const form = new FormApi({
defaultValues: {
Expand Down
78 changes: 77 additions & 1 deletion packages/react-form/src/tests/useField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react'
import { render, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import { createFormFactory } from '..'
import { type FormApi, createFormFactory } from '..'
import { sleep } from './utils'

const user = userEvent.setup()
Expand Down Expand Up @@ -373,4 +373,80 @@ describe('useField', () => {
await waitFor(() => getByText(error))
expect(getByText(error)).toBeInTheDocument()
})

it('should preserve value when preserve value property is true', async () => {
type Person = {
firstName: string
lastName: string
}
const formFactory = createFormFactory<Person, unknown>()
let form: FormApi<Person, unknown> | null = null
function Comp() {
form = formFactory.useForm()
return (
<form.Provider>
<form.Field
name="firstName"
defaultValue="hello"
preserveValue={true}
children={(field) => {
return (
<input
data-testid="fieldinput"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
)
}}
/>
</form.Provider>
)
}

const { getByTestId, unmount, rerender } = render(<Comp />)
const input = getByTestId('fieldinput')
expect(input).toHaveValue('hello')
await user.type(input, 'world')
unmount()
expect(form!.fieldInfo['firstName']).toBeDefined()
})

it('should not preserve value when preserve value property is false', async () => {
type Person = {
firstName: string
lastName: string
}
const formFactory = createFormFactory<Person, unknown>()
let form: FormApi<Person, unknown> | null = null
function Comp() {
form = formFactory.useForm()
return (
<form.Provider>
<form.Field
name="firstName"
defaultValue="hello"
preserveValue={false}
children={(field) => {
return (
<input
data-testid="fieldinput"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
)
}}
/>
</form.Provider>
)
}

const { getByTestId, unmount } = render(<Comp />)
const input = getByTestId('fieldinput')
expect(input).toHaveValue('hello')
unmount()
const info = form!.fieldInfo
expect(Object.keys(info)).toHaveLength(0)
})
})

0 comments on commit 57bc462

Please sign in to comment.