Skip to content

feat(form-core): add array method field.filterValues and form.filterFieldValues #1426

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
21 changes: 21 additions & 0 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,27 @@ export class FieldApi<
this.triggerOnChangeListener()
}

/**
* Filter values in the array using the provided predicate callback.
* @param predicate — The predicate callback to pass to the array's filter function.
* @param opts
*/
filterValues = (
predicate: (
value: TData extends Array<any> ? TData[number] : never,
index: number,
array: TData,
) => boolean,
opts?: UpdateMetaOptions & {
/** `thisArg` — An object to which the `this` keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the `this` value. */
thisArg?: any
},
) => {
this.form.filterFieldValues(this.name, predicate, opts)

this.triggerOnChangeListener()
}

/**
* @private
*/
Expand Down
85 changes: 81 additions & 4 deletions packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1870,7 +1870,7 @@
await this.validateField(field, 'change')

// Shift down all meta after validating to make sure the new field has been mounted
metaHelper(this).handleArrayFieldMetaShift(field, index, 'insert')
metaHelper(this).handleArrayInsert(field, index)

await this.validateArrayFieldsStartingFrom(field, index, 'change')
}
Expand Down Expand Up @@ -1926,7 +1926,7 @@
)

// Shift up all meta
metaHelper(this).handleArrayFieldMetaShift(field, index, 'remove')
metaHelper(this).handleArrayRemove(field, index)

if (lastIndex !== null) {
const start = `${field}[${lastIndex}]`
Expand Down Expand Up @@ -1958,7 +1958,7 @@
)

// Swap meta
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'swap', index2)
metaHelper(this).handleArraySwap(field, index1, index2)

// Validate the whole array
this.validateField(field, 'change')
Expand Down Expand Up @@ -1986,7 +1986,7 @@
)

// Move meta between index1 and index2
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'move', index2)
metaHelper(this).handleArrayMove(field, index1, index2)

// Validate the whole array
this.validateField(field, 'change')
Expand All @@ -1995,6 +1995,83 @@
this.validateField(`${field}[${index2}]` as DeepKeys<TFormData>, 'change')
}

/**
* Filter the array values by the provided predicate.
* @param field
* @param predicate — The predicate callback to pass to the array's filter function.
* @param opts
*/
filterFieldValues = <
TField extends DeepKeys<TFormData>,
TData extends DeepValue<TFormData, TField>,
>(
field: TField,
predicate: (
value: TData extends Array<any> ? TData[number] : never,
index: number,
array: TData,
) => boolean,
opts?: UpdateMetaOptions & {
/** `thisArg` — An object to which the `this` keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the `this` value. */
thisArg?: any
},
) => {
const { thisArg, ...metaOpts } = opts ?? {}
const fieldValue = this.getFieldValue(field)

const arrayData = {
previousLength: Array.isArray(fieldValue)
? (fieldValue as unknown[]).length
: null,

Check warning on line 2025 in packages/form-core/src/FormApi.ts

View check run for this annotation

Codecov / codecov/patch

packages/form-core/src/FormApi.ts#L2025

Added line #L2025 was not covered by tests
validateFromIndex: null as number | null,
}

const remainingIndeces: number[] = []

const filterFunction =
opts?.thisArg === undefined ? predicate : predicate.bind(opts.thisArg)

this.setFieldValue(
field,
(prev: any) =>
prev.filter((value: any, index: number, array: TData) => {
const keepElement = filterFunction(value, index, array)
if (!keepElement) {
// remember the first index that got filtered
arrayData.validateFromIndex ??= index
return false
}
remainingIndeces.push(index)
return true
}),
metaOpts,
)

// Shift meta accounting for filtered values
metaHelper(this).handleArrayFilter(field, remainingIndeces)

// remove dangling fields if the filter call reduced the length of the array
if (
arrayData.previousLength !== null &&
remainingIndeces.length !== arrayData.previousLength
) {
for (let i = remainingIndeces.length; i < arrayData.previousLength; i++) {
const fieldKey = `${field}[${i}]`
this.deleteField(fieldKey as never)
}
}

// validate the array and the fields starting from the shifted elements
this.validateField(field, 'change')
if (arrayData.validateFromIndex !== null) {
this.validateArrayFieldsStartingFrom(
field,
arrayData.validateFromIndex,
'change',
)
}
}

/**
* Resets the field value and meta to default state
*/
Expand Down
Loading