Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/easy-rice-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/form-core': patch
---

Improve field snapshot and metadata re-use strategy.
13 changes: 12 additions & 1 deletion packages/form-core/src/FieldApi/FieldApi.lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import type {
} from '../validation.public'
import type {
ChildContributionStates,
DerivedMetaMarkers,
FieldAtoms,
InternalBaseFieldMeta,
InternalFieldMeta,
Expand Down Expand Up @@ -348,11 +349,21 @@ export class InternalFieldApi<
metaAtom = createAtom(defaultInternalBaseFieldMeta)
}
if (!storeAtom) {
const markers: DerivedMetaMarkers = {
source: undefined,
canDisplayErrors: undefined,
}
storeAtom = createAtom<InternalFieldState>((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
Expand Down
59 changes: 19 additions & 40 deletions packages/form-core/src/FieldApi/fieldState.lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any, any> {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -152,10 +152,12 @@ export function deriveFromBaseFieldMeta(

if (
previousMeta &&
markers &&
canReusePreviousMeta({
baseMeta,
canDisplayErrors,
isDefaultValue,
markers,
originalErrors,
previousMeta,
})
Expand Down Expand Up @@ -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<ValidationIssue>
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

Expand Down
21 changes: 9 additions & 12 deletions packages/form-core/src/utils.lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading