diff --git a/.changeset/easy-rice-live.md b/.changeset/easy-rice-live.md new file mode 100644 index 000000000..46ca06595 --- /dev/null +++ b/.changeset/easy-rice-live.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +Improve field snapshot and metadata re-use strategy. diff --git a/packages/form-core/src/FieldApi/FieldApi.lib.ts b/packages/form-core/src/FieldApi/FieldApi.lib.ts index 6fdfbed98..674f2461b 100644 --- a/packages/form-core/src/FieldApi/FieldApi.lib.ts +++ b/packages/form-core/src/FieldApi/FieldApi.lib.ts @@ -61,6 +61,7 @@ import type { } from '../validation.public' import type { ChildContributionStates, + DerivedMetaMarkers, FieldAtoms, InternalBaseFieldMeta, InternalFieldMeta, @@ -348,11 +349,21 @@ export class InternalFieldApi< metaAtom = createAtom(defaultInternalBaseFieldMeta) } if (!storeAtom) { + const markers: DerivedMetaMarkers = { + source: undefined, + canDisplayErrors: undefined, + } storeAtom = createAtom((prev) => { const newMeta = metaAtom.get() const value = this._getValue() - const meta = deriveFromBaseFieldMeta(newMeta, prev?.meta, this, value) + const meta = deriveFromBaseFieldMeta( + newMeta, + prev?.meta, + this, + value, + markers, + ) if (prev?.meta === meta && prev.value === value) { return prev diff --git a/packages/form-core/src/FieldApi/fieldState.lib.ts b/packages/form-core/src/FieldApi/fieldState.lib.ts index da58f07e6..c213d596f 100644 --- a/packages/form-core/src/FieldApi/fieldState.lib.ts +++ b/packages/form-core/src/FieldApi/fieldState.lib.ts @@ -49,14 +49,13 @@ export interface FormGroupFieldErrorMeta { export interface InternalBaseFieldMeta extends BaseFieldMeta, MetaExtension {} export interface InternalFieldMeta extends AnyPublicFieldMeta, MetaExtension {} -const derivedMetaSourceKey = Symbol('tanstack-form-derived-meta-source') -const derivedMetaCanDisplayErrorsKey = Symbol( - 'tanstack-form-derived-meta-can-display-errors', -) - -type DerivedMetaMarkers = { - [derivedMetaCanDisplayErrorsKey]?: boolean - [derivedMetaSourceKey]?: InternalBaseFieldMeta +/** + * @private + * Used to track the prev meta for a field + */ +export interface DerivedMetaMarkers { + source: InternalBaseFieldMeta | undefined + canDisplayErrors: boolean | undefined } export interface InternalFieldState extends PublicFieldState { @@ -118,6 +117,7 @@ export function deriveFromBaseFieldMeta( previousMeta: InternalFieldMeta | undefined, field: AnyInternalFieldApi | undefined, value?: any, + markers?: DerivedMetaMarkers, ): InternalFieldMeta { const isDefaultValue = field ? field._getIsDefaultValue(value) : true const errorVisibility = getErrorVisibility(field) @@ -152,10 +152,12 @@ export function deriveFromBaseFieldMeta( if ( previousMeta && + markers && canReusePreviousMeta({ baseMeta, canDisplayErrors, isDefaultValue, + markers, originalErrors, previousMeta, }) @@ -184,55 +186,32 @@ export function deriveFromBaseFieldMeta( subfields, isPristine: !isDirty, } - return markDerivedMeta(result, baseMeta, canDisplayErrors) -} -function markDerivedMeta( - meta: InternalFieldMeta, - baseMeta: InternalBaseFieldMeta, - canDisplayErrors: boolean, -): InternalFieldMeta { - Object.defineProperties(meta, { - [derivedMetaCanDisplayErrorsKey]: { - value: canDisplayErrors, - }, - [derivedMetaSourceKey]: { - value: baseMeta, - }, - }) - - return meta -} - -function getDerivedMetaSource( - meta: (InternalFieldMeta & DerivedMetaMarkers) | undefined, -): InternalBaseFieldMeta | undefined { - return meta?.[derivedMetaSourceKey] -} + if (markers) { + markers.source = baseMeta + markers.canDisplayErrors = canDisplayErrors + } -function getDerivedMetaCanDisplayErrors( - meta: (InternalFieldMeta & DerivedMetaMarkers) | undefined, -): boolean | undefined { - return meta?.[derivedMetaCanDisplayErrorsKey] + return result } function canReusePreviousMeta({ baseMeta, canDisplayErrors, isDefaultValue, + markers, originalErrors, previousMeta, }: { baseMeta: InternalBaseFieldMeta canDisplayErrors: boolean isDefaultValue: boolean + markers: DerivedMetaMarkers originalErrors: Array previousMeta: InternalFieldMeta }): boolean { - if (getDerivedMetaSource(previousMeta) !== baseMeta) return false - if (getDerivedMetaCanDisplayErrors(previousMeta) !== canDisplayErrors) { - return false - } + if (markers.source !== baseMeta) return false + if (markers.canDisplayErrors !== canDisplayErrors) return false if (previousMeta.isDefaultValue !== isDefaultValue) return false if (previousMeta.original.errors !== originalErrors) return false diff --git a/packages/form-core/src/utils.lib.ts b/packages/form-core/src/utils.lib.ts index bbf5a3de1..3c4533d14 100644 --- a/packages/form-core/src/utils.lib.ts +++ b/packages/form-core/src/utils.lib.ts @@ -29,30 +29,27 @@ export function nameToFieldNodeSegments( if (typeof nameOrSegments !== 'string') return nameOrSegments.slice() const result: NameSegments = [] - let s = '' + let start = 0 for (let i = 0; i < nameOrSegments.length; i++) { switch (nameOrSegments.charCodeAt(i)) { case 0x2e: // '.' case 0x5b: // '[' - if (s.length > 0) { - result.push(s) - s = '' + if (i > start) { + result.push(nameOrSegments.slice(start, i)) } + start = i + 1 break case 0x5d: // ']' - if (s.length > 0) { - result.push(parseInt(s, 10)) - s = '' + if (i > start) { + result.push(parseInt(nameOrSegments.slice(start, i), 10)) } - break - default: - s += nameOrSegments.charAt(i) + start = i + 1 break } } - if (s.length > 0) { - result.push(s) + if (start < nameOrSegments.length) { + result.push(nameOrSegments.slice(start)) } return result