Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@tanstack/form-core

Package Overview
Dependencies
Maintainers
2
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/form-core - npm Package Compare versions

Comparing version 0.20.2 to 0.20.3

2

dist/esm/FieldApi.d.ts

@@ -86,3 +86,3 @@ import { Store } from '@tanstack/store';

pushValue: (value: TData extends any[] ? TData[number] : never) => void;
insertValue: (index: number, value: TData extends any[] ? TData[number] : never) => void;
insertValue: (index: number, value: TData extends any[] ? TData[number] : never) => Promise<void>;
removeValue: (index: number, opts?: {

@@ -89,0 +89,0 @@ touch: boolean;

@@ -84,5 +84,3 @@ import { Store } from "@tanstack/store";

this.insertValue = (index, value) => this.form.insertFieldValue(this.name, index, value);
this.removeValue = async (index, opts2) => {
await this.form.removeFieldValue(this.name, index, opts2);
};
this.removeValue = (index, opts2) => this.form.removeFieldValue(this.name, index, opts2);
this.swapValues = (aIndex, bIndex) => this.form.swapFieldValues(this.name, aIndex, bIndex);

@@ -89,0 +87,0 @@ this.getLinkedFields = (cause) => {

@@ -96,2 +96,4 @@ import { Store } from '@tanstack/store';

validateAllFields: (cause: ValidationCause) => Promise<ValidationError[]>;
validateArrayFieldsStartingFrom: <TField extends DeepKeys<TFormData>>(field: TField, index: number, cause: ValidationCause) => Promise<ValidationError[]>;
validateField: <TField extends DeepKeys<TFormData>>(field: TField, cause: ValidationCause) => ValidationError[] | Promise<ValidationError[]>;
validateSync: (cause: ValidationCause) => {

@@ -117,3 +119,3 @@ hasErrored: boolean;

touch?: boolean;
}) => void;
}) => Promise<void>;
removeFieldValue: <TField extends DeepKeys<TFormData>>(field: TField, index: number, opts?: {

@@ -120,0 +122,0 @@ touch?: boolean;

@@ -110,2 +110,33 @@ import { Store } from "@tanstack/store";

};
this.validateArrayFieldsStartingFrom = async (field, index, cause) => {
const currentValue = this.getFieldValue(field);
const lastIndex = Array.isArray(currentValue) ? Math.max(currentValue.length - 1, 0) : null;
const fieldKeysToValidate = [`${field}[${index}]`];
for (let i = index + 1; i <= (lastIndex ?? 0); i++) {
fieldKeysToValidate.push(`${field}[${i}]`);
}
const fieldsToValidate = Object.keys(this.fieldInfo).filter(
(fieldKey) => fieldKeysToValidate.some((key) => fieldKey.startsWith(key))
);
const fieldValidationPromises = [];
this.store.batch(() => {
fieldsToValidate.forEach((nestedField) => {
fieldValidationPromises.push(
Promise.resolve().then(() => this.validateField(nestedField, cause))
);
});
});
const fieldErrorMapMap = await Promise.all(fieldValidationPromises);
return fieldErrorMapMap.flat();
};
this.validateField = (field, cause) => {
var _a2;
const fieldInstance = (_a2 = this.fieldInfo[field]) == null ? void 0 : _a2.instance;
if (!fieldInstance)
return [];
if (!fieldInstance.state.meta.isTouched) {
fieldInstance.setMeta((prev) => ({ ...prev, isTouched: true }));
}
return fieldInstance.validate(cause);
};
this.validateSync = (cause) => {

@@ -346,3 +377,3 @@ const validates = getSyncValidatorArray(cause, this.options);

this.pushFieldValue = (field, value, opts2) => {
return this.setFieldValue(
this.setFieldValue(
field,

@@ -352,4 +383,5 @@ (prev) => [...Array.isArray(prev) ? prev : [], value],

);
this.validateField(field, "change");
};
this.insertFieldValue = (field, index, value, opts2) => {
this.insertFieldValue = async (field, index, value, opts2) => {
this.setFieldValue(

@@ -364,2 +396,4 @@ field,

);
await this.validateField(field, "change");
await this.validateArrayFieldsStartingFrom(field, index, "change");
};

@@ -385,3 +419,4 @@ this.removeFieldValue = async (field, index, opts2) => {

}
await this.validateAllFields("change");
await this.validateField(field, "change");
await this.validateArrayFieldsStartingFrom(field, index, "change");
};

@@ -394,2 +429,5 @@ this.swapFieldValues = (field, index1, index2) => {

});
this.validateField(field, "change");
this.validateField(`${field}[${index1}]`, "change");
this.validateField(`${field}[${index2}]`, "change");
};

@@ -401,2 +439,5 @@ this.moveFieldValues = (field, index1, index2) => {

});
this.validateField(field, "change");
this.validateField(`${field}[${index1}]`, "change");
this.validateField(`${field}[${index2}]`, "change");
};

@@ -403,0 +444,0 @@ this.store = new Store(

{
"name": "@tanstack/form-core",
"version": "0.20.2",
"version": "0.20.3",
"description": "Powerful, type-safe, framework agnostic forms.",

@@ -5,0 +5,0 @@ "author": "tannerlinsley",

@@ -480,5 +480,4 @@ import { Store } from '@tanstack/store'

removeValue = async (index: number, opts?: { touch: boolean }) => {
await this.form.removeFieldValue(this.name, index, opts)
}
removeValue = (index: number, opts?: { touch: boolean }) =>
this.form.removeFieldValue(this.name, index, opts)

@@ -485,0 +484,0 @@ swapValues = (aIndex: number, bIndex: number) =>

@@ -373,2 +373,55 @@ import { Store } from '@tanstack/store'

validateArrayFieldsStartingFrom = async <TField extends DeepKeys<TFormData>>(
field: TField,
index: number,
cause: ValidationCause,
) => {
const currentValue = this.getFieldValue(field)
const lastIndex = Array.isArray(currentValue)
? Math.max(currentValue.length - 1, 0)
: null
// We have to validate all fields that have shifted (at least the current field)
const fieldKeysToValidate = [`${field}[${index}]`]
for (let i = index + 1; i <= (lastIndex ?? 0); i++) {
fieldKeysToValidate.push(`${field}[${i}]`)
}
// We also have to include all fields that are nested in the shifted fields
const fieldsToValidate = Object.keys(this.fieldInfo).filter((fieldKey) =>
fieldKeysToValidate.some((key) => fieldKey.startsWith(key)),
) as DeepKeys<TFormData>[]
// Validate the fields
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
this.store.batch(() => {
fieldsToValidate.forEach((nestedField) => {
fieldValidationPromises.push(
Promise.resolve().then(() => this.validateField(nestedField, cause)),
)
})
})
const fieldErrorMapMap = await Promise.all(fieldValidationPromises)
return fieldErrorMapMap.flat()
}
validateField = <TField extends DeepKeys<TFormData>>(
field: TField,
cause: ValidationCause,
) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const fieldInstance = this.fieldInfo[field]?.instance
if (!fieldInstance) return []
// If the field is not touched (same logic as in validateAllFields)
if (!fieldInstance.state.meta.isTouched) {
// Mark it as touched
fieldInstance.setMeta((prev) => ({ ...prev, isTouched: true }))
}
return fieldInstance.validate(cause)
}
// TODO: This code is copied from FieldApi, we should refactor to share

@@ -693,3 +746,3 @@ validateSync = (cause: ValidationCause) => {

) => {
return this.setFieldValue(
this.setFieldValue(
field,

@@ -699,5 +752,6 @@ (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,

)
this.validateField(field, 'change')
}
insertFieldValue = <TField extends DeepKeys<TFormData>>(
insertFieldValue = async <TField extends DeepKeys<TFormData>>(
field: TField,

@@ -719,2 +773,6 @@ index: number,

)
// Validate the whole array + all fields that have shifted
await this.validateField(field, 'change')
await this.validateArrayFieldsStartingFrom(field, index, 'change')
}

@@ -753,3 +811,5 @@

await this.validateAllFields('change')
// Validate the whole array + all fields that have shifted
await this.validateField(field, 'change')
await this.validateArrayFieldsStartingFrom(field, index, 'change')
}

@@ -767,2 +827,8 @@

})
// Validate the whole array
this.validateField(field, 'change')
// Validate the swapped fields
this.validateField(`${field}[${index1}]` as DeepKeys<TFormData>, 'change')
this.validateField(`${field}[${index2}]` as DeepKeys<TFormData>, 'change')
}

@@ -779,2 +845,8 @@

})
// Validate the whole array
this.validateField(field, 'change')
// Validate the moved fields
this.validateField(`${field}[${index1}]` as DeepKeys<TFormData>, 'change')
this.validateField(`${field}[${index2}]` as DeepKeys<TFormData>, 'change')
}

@@ -781,0 +853,0 @@ }

@@ -110,2 +110,31 @@ import { describe, expect, it, vi } from 'vitest'

it('should run onChange validation when pushing an array fields value', async () => {
const form = new FormApi({
defaultValues: {
names: ['test'],
},
})
form.mount()
const field = new FieldApi({
form,
name: 'names',
validators: {
onChange: ({ value }) => {
if (value.length < 3) {
return 'At least 3 names are required'
}
return
},
},
})
field.mount()
field.pushValue('other')
expect(field.getMeta().errors).toStrictEqual([
'At least 3 names are required',
])
})
it('should insert a value into an array value correctly', () => {

@@ -128,2 +157,34 @@ const form = new FormApi({

it('should run onChange validation when inserting an array fields value', () => {
const form = new FormApi({
defaultValues: {
names: ['test'],
},
})
form.mount()
const field = new FieldApi({
form,
name: 'names',
validators: {
onChange: ({ value }) => {
if (value.length < 3) {
return 'At least 3 names are required'
}
return
},
},
defaultMeta: {
isTouched: true,
},
})
field.mount()
field.insertValue(1, 'other')
expect(field.getMeta().errors).toStrictEqual([
'At least 3 names are required',
])
})
it('should remove a value from an array value correctly', () => {

@@ -146,2 +207,34 @@ const form = new FormApi({

it('should run onChange validation when removing an array fields value', async () => {
const form = new FormApi({
defaultValues: {
names: ['test'],
},
})
form.mount()
const field = new FieldApi({
form,
name: 'names',
validators: {
onChange: ({ value }) => {
if (value.length < 3) {
return 'At least 3 names are required'
}
return
},
},
defaultMeta: {
isTouched: true,
},
})
field.mount()
await field.removeValue(0)
expect(field.getMeta().errors).toStrictEqual([
'At least 3 names are required',
])
})
it('should remove a subfield from an array field correctly', async () => {

@@ -275,2 +368,34 @@ const form = new FormApi({

it('should run onChange validation when swapping an array fields value', () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2'],
},
})
form.mount()
const field = new FieldApi({
form,
name: 'names',
validators: {
onChange: ({ value }) => {
if (value.length < 3) {
return 'At least 3 names are required'
}
return
},
},
defaultMeta: {
isTouched: true,
},
})
field.mount()
field.swapValues(0, 1)
expect(field.getMeta().errors).toStrictEqual([
'At least 3 names are required',
])
})
it('should move a value from an array value correctly', () => {

@@ -293,2 +418,34 @@ const form = new FormApi({

it('should run onChange validation when moving an array fields value', () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2'],
},
})
form.mount()
const field = new FieldApi({
form,
name: 'names',
validators: {
onChange: ({ value }) => {
if (value.length < 3) {
return 'At least 3 names are required'
}
return
},
},
defaultMeta: {
isTouched: true,
},
})
field.mount()
field.moveValue(0, 1)
expect(field.getMeta().errors).toStrictEqual([
'At least 3 names are required',
])
})
it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {

@@ -295,0 +452,0 @@ const form = new FormApi({

@@ -327,2 +327,21 @@ import { describe, expect, it, vi } from 'vitest'

it("should run onChange validation when pushing an array field's value", () => {
const form = new FormApi({
defaultValues: {
names: ['test'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
form.pushFieldValue('names', 'other')
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
})
it("should insert an array field's value", () => {

@@ -340,2 +359,52 @@ const form = new FormApi({

it("should run onChange validation when inserting an array field's value", () => {
const form = new FormApi({
defaultValues: {
names: ['test'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
form.insertFieldValue('names', 1, 'other')
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
})
it("should validate all shifted fields when inserting an array field's value", async () => {
const form = new FormApi({
defaultValues: {
names: [{ first: 'test' }, { first: 'test2' }],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
const field1 = new FieldApi({
form,
name: 'names[0].first',
defaultValue: 'test',
validators: {
onChange: ({ value }) => value !== 'test' && 'Invalid value',
},
})
field1.mount()
expect(field1.state.meta.errors).toStrictEqual([])
await form.insertFieldValue('names', 0, { first: 'other' })
expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
})
it("should remove an array field's value", () => {

@@ -353,2 +422,75 @@ const form = new FormApi({

it("should run onChange validation when removing an array field's value", () => {
const form = new FormApi({
defaultValues: {
names: ['test'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 1 ? undefined : 'At least 1 name is required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
form.removeFieldValue('names', 0)
expect(form.state.errors).toStrictEqual(['At least 1 name is required'])
})
it("should validate following fields when removing an array field's value", async () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2', 'test3'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 1 ? undefined : 'At least 1 name is required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
const field1 = new FieldApi({
form,
name: 'names[0]',
defaultValue: 'test',
validators: {
onChange: ({ value }) => value !== 'test' && 'Invalid value',
},
})
field1.mount()
const field2 = new FieldApi({
form,
name: 'names[1]',
defaultValue: 'test2',
validators: {
onChange: ({ value }) => value !== 'test2' && 'Invalid value',
},
})
field2.mount()
const field3 = new FieldApi({
form,
name: 'names[2]',
defaultValue: 'test3',
validators: {
onChange: ({ value }) => value !== 'test3' && 'Invalid value',
},
})
field3.mount()
expect(field1.state.meta.errors).toStrictEqual([])
expect(field2.state.meta.errors).toStrictEqual([])
expect(field3.state.meta.errors).toStrictEqual([])
await form.removeFieldValue('names', 1)
expect(field1.state.meta.errors).toStrictEqual([])
expect(field2.state.meta.errors).toStrictEqual(['Invalid value'])
// This field does not exist anymore. Therefore, its validation should also not run
expect(field3.state.meta.errors).toStrictEqual([])
})
it("should swap an array field's value", () => {

@@ -361,2 +503,5 @@ const form = new FormApi({

form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
form.swapFieldValues('names', 1, 2)

@@ -367,2 +512,134 @@

it("should run onChange validation when swapping an array field's value", () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
expect(form.state.errors).toStrictEqual([])
form.swapFieldValues('names', 1, 2)
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
})
it('should run validation on swapped fields', () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
const field1 = new FieldApi({
form,
name: 'names[0]',
defaultValue: 'test',
validators: {
onChange: ({ value }) => value !== 'test' && 'Invalid value',
},
})
field1.mount()
const field2 = new FieldApi({
form,
name: 'names[1]',
defaultValue: 'test2',
})
field2.mount()
expect(field1.state.meta.errors).toStrictEqual([])
expect(field2.state.meta.errors).toStrictEqual([])
form.swapFieldValues('names', 0, 1)
expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
expect(field2.state.meta.errors).toStrictEqual([])
})
it("should move an array field's value", () => {
const form = new FormApi({
defaultValues: {
names: ['one', 'two', 'three'],
},
})
form.mount()
form.moveFieldValues('names', 1, 2)
expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
})
it("should run onChange validation when moving an array field's value", () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
expect(form.state.errors).toStrictEqual([])
form.moveFieldValues('names', 0, 1)
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
})
it('should run validation on moved fields', () => {
const form = new FormApi({
defaultValues: {
names: ['test', 'test2'],
},
validators: {
onChange: ({ value }) =>
value.names.length > 3 ? undefined : 'At least 3 names are required',
},
})
form.mount()
// Since validation runs through the field, a field must be mounted for that array
new FieldApi({ form, name: 'names' }).mount()
const field1 = new FieldApi({
form,
name: 'names[0]',
defaultValue: 'test',
validators: {
onChange: ({ value }) => value !== 'test' && 'Invalid value',
},
})
field1.mount()
const field2 = new FieldApi({
form,
name: 'names[1]',
defaultValue: 'test2',
})
field2.mount()
expect(field1.state.meta.errors).toStrictEqual([])
expect(field2.state.meta.errors).toStrictEqual([])
form.swapFieldValues('names', 0, 1)
expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
expect(field2.state.meta.errors).toStrictEqual([])
})
it('should handle fields inside an array', async () => {

@@ -1060,2 +1337,31 @@ interface Employee {

it('should validate a single field consistently if touched', async () => {
const form = new FormApi({
defaultValues: {
firstName: '',
lastName: '',
},
})
const field = new FieldApi({
form,
name: 'firstName',
validators: {
onChange: ({ value }) =>
value.length > 0 ? undefined : 'first name is required',
},
defaultMeta: {
isTouched: true,
},
})
field.mount()
form.mount()
await form.validateField('firstName', 'change')
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
await form.validateField('firstName', 'change')
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
})
it('should show onSubmit errors', async () => {

@@ -1062,0 +1368,0 @@ const form = new FormApi({

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc