Skip to content

fix(react-form): removing array items (#1171) #1319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 31, 2025
Merged
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
30 changes: 15 additions & 15 deletions docs/reference/classes/fieldapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ The current field state.
getInfo(): FieldInfo<TParentData>
```

Defined in: [packages/form-core/src/FieldApi.ts:1217](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1217)
Defined in: [packages/form-core/src/FieldApi.ts:1222](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1222)

Gets the field information object.

Expand All @@ -179,7 +179,7 @@ Gets the field information object.
getMeta(): FieldMeta<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync>
```

Defined in: [packages/form-core/src/FieldApi.ts:1185](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1185)
Defined in: [packages/form-core/src/FieldApi.ts:1190](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1190)

#### Returns

Expand All @@ -193,7 +193,7 @@ Defined in: [packages/form-core/src/FieldApi.ts:1185](https://github.com/TanStac
getValue(): TData
```

Defined in: [packages/form-core/src/FieldApi.ts:1167](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1167)
Defined in: [packages/form-core/src/FieldApi.ts:1172](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1172)

Gets the current field value.

Expand All @@ -213,7 +213,7 @@ Use `field.state.value` instead.
handleBlur(): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1627](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1627)
Defined in: [packages/form-core/src/FieldApi.ts:1632](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1632)

Handles the blur event.

Expand All @@ -229,7 +229,7 @@ Handles the blur event.
handleChange(updater): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1620](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1620)
Defined in: [packages/form-core/src/FieldApi.ts:1625](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1625)

Handles the change event.

Expand All @@ -254,7 +254,7 @@ insertValue(
opts?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1237](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1237)
Defined in: [packages/form-core/src/FieldApi.ts:1242](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1242)

Inserts a value at the specified index, shifting the subsequent values to the right.

Expand Down Expand Up @@ -307,7 +307,7 @@ moveValue(
opts?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1293](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1293)
Defined in: [packages/form-core/src/FieldApi.ts:1298](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1298)

Moves the value at the first specified index to the second specified index.

Expand Down Expand Up @@ -337,7 +337,7 @@ Moves the value at the first specified index to the second specified index.
pushValue(value, opts?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1222](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1222)
Defined in: [packages/form-core/src/FieldApi.ts:1227](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1227)

Pushes a new value to the field.

Expand All @@ -363,7 +363,7 @@ Pushes a new value to the field.
removeValue(index, opts?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1269](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1269)
Defined in: [packages/form-core/src/FieldApi.ts:1274](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1274)

Removes a value at the specified index.

Expand Down Expand Up @@ -392,7 +392,7 @@ replaceValue(
opts?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1253](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1253)
Defined in: [packages/form-core/src/FieldApi.ts:1258](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1258)

Replaces a value at the specified index.

Expand Down Expand Up @@ -422,7 +422,7 @@ Replaces a value at the specified index.
setErrorMap(errorMap): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1647](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1647)
Defined in: [packages/form-core/src/FieldApi.ts:1652](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1652)

Updates the field's errorMap

Expand All @@ -444,7 +444,7 @@ Updates the field's errorMap
setMeta(updater): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1190](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1190)
Defined in: [packages/form-core/src/FieldApi.ts:1195](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1195)

Sets the field metadata.

Expand All @@ -466,7 +466,7 @@ Sets the field metadata.
setValue(updater, options?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1174](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1174)
Defined in: [packages/form-core/src/FieldApi.ts:1179](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1179)

Sets the field value and run the `change` validator.

Expand Down Expand Up @@ -495,7 +495,7 @@ swapValues(
opts?): void
```

Defined in: [packages/form-core/src/FieldApi.ts:1281](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1281)
Defined in: [packages/form-core/src/FieldApi.ts:1286](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1286)

Swaps the values at the specified indices.

Expand Down Expand Up @@ -547,7 +547,7 @@ Updates the field instance with new options.
validate(cause, opts?): unknown[] | Promise<unknown[]>
```

Defined in: [packages/form-core/src/FieldApi.ts:1587](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1587)
Defined in: [packages/form-core/src/FieldApi.ts:1592](https://github.com/TanStack/form/blob/main/packages/form-core/src/FieldApi.ts#L1592)

Validates the field value.

Expand Down
21 changes: 13 additions & 8 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1135,17 +1135,25 @@
TParentSubmitMeta
>,
) => {
// Default Value
this.options = opts as never

const nameHasChanged = this.name !== opts.name
this.name = opts.name

// Default Value
if (this.state.value === undefined) {
const formDefault = getBy(opts.form.options.defaultValues, opts.name)

if (opts.defaultValue !== undefined) {
this.setValue(opts.defaultValue as never, {
const defaultValue = opts.defaultValue ?? formDefault

// The name is dynamic in array fields. It changes when the user performs operations like removing or reordering.
// In this case, we don't want to force a default value if the store managed to find an existing value.
if (nameHasChanged) {
this.setValue((val) => val || defaultValue, {
dontUpdateMeta: true,
})
} else if (formDefault !== undefined) {
this.setValue(formDefault as never, {
} else if (defaultValue !== undefined) {
this.setValue(defaultValue as never, {

Check warning on line 1156 in packages/form-core/src/FieldApi.ts

View check run for this annotation

Codecov / codecov/patch

packages/form-core/src/FieldApi.ts#L1156

Added line #L1156 was not covered by tests
dontUpdateMeta: true,
})
}
Expand All @@ -1155,9 +1163,6 @@
if (this.form.getFieldMeta(this.name) === undefined) {
this.setMeta(this.state.meta)
}

this.options = opts as never
this.name = opts.name
}

/**
Expand Down
97 changes: 96 additions & 1 deletion packages/react-form/tests/useField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react-compiler/react-compiler */
import { describe, expect, it, vi } from 'vitest'
import { render, waitFor } from '@testing-library/react'
import { render, waitFor, within } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { StrictMode, useState } from 'react'
import { useStore } from '@tanstack/react-store'
Expand Down Expand Up @@ -1070,6 +1070,101 @@ describe('useField', () => {
expect(getByText(`["Test"]`)).toBeInTheDocument()
})

it('should handle removing element from array', async () => {
type Person = {
name: string
id: number
}

const fakePeople = {
jack: {
id: 5,
name: 'Jack',
},
molly: {
id: 6,
name: 'Molly',
},
george: {
id: 7,
name: 'George',
},
} satisfies Record<string, Person>

function Comp() {
const form = useForm({
defaultValues: {
people: [fakePeople.jack, fakePeople.molly, fakePeople.george],
},
})

return (
<form.Field name="people" mode="array">
{(field) => {
return (
<div>
<div data-testid="container">
{field.state.value.map((item, i) => {
return (
<form.Field key={item.id} name={`people[${i}].name`}>
{(subField) => {
return (
<div>
<label>
<div>Name for person {i}</div>
<span>{subField.state.value}</span>
<input
name={subField.name}
value={subField.state.value}
onChange={(e) =>
subField.handleChange(e.target.value)
}
/>
</label>
</div>
)
}}
</form.Field>
)
})}
</div>
<button onClick={() => field.removeValue(1)} type="button">
Remove person
</button>
</div>
)
}}
</form.Field>
)
}

const { getByText, queryByText, getByTestId } = render(
<StrictMode>
<Comp />
</StrictMode>,
)

let exisingPeople: Person[] = [
fakePeople.jack,
fakePeople.molly,
fakePeople.george,
]
exisingPeople.forEach((person) =>
expect(getByText(person.name)).toBeInTheDocument(),
)
const container = getByTestId('container')
expect(within(container).getAllByRole('textbox')).toHaveLength(3)

await user.click(getByText('Remove person'))

expect(within(container).getAllByRole('textbox')).toHaveLength(2)
exisingPeople = [fakePeople.jack, fakePeople.george]
exisingPeople.forEach((person) =>
expect(getByText(person.name)).toBeInTheDocument(),
)
expect(queryByText(fakePeople.molly.name)).not.toBeInTheDocument()
})

it('should not rerender unrelated fields', async () => {
const renderCount = {
field1: 0,
Expand Down