Skip to content

Commit 7f39e6d

Browse files
committed
fix: do second pass of fixes
1 parent 235441a commit 7f39e6d

4 files changed

Lines changed: 67 additions & 1 deletion

File tree

.changeset/tasty-humans-joke.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
'@tanstack/form-core': patch
33
---
44

5-
Refactor: Migrate Form Groups's storage and accessing
5+
Refactor: Store Form Groups on trie nodes instead of the form instance

packages/form-core/src/FieldApi/fieldTree.lib.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,22 @@ function notifyFieldSubtreeListeners(
265265
function prepareFormGroupsForFieldReplacement(
266266
fields: ReadonlyArray<AnyInternalFieldApi>,
267267
): () => void {
268+
const fieldsToReplace = new Set(fields)
268269
const formGroups = fields.flatMap((field) =>
269270
field._formGroup ? [{ group: field._formGroup, name: field.name }] : [],
270271
)
272+
const affectedFormGroups = new Set(formGroups.map(({ group }) => group))
273+
274+
const replacementRoot = fields[0]
275+
if (replacementRoot) {
276+
visitFieldAndAncestors(replacementRoot, (field) => {
277+
if (field._formGroup) affectedFormGroups.add(field._formGroup)
278+
})
279+
}
280+
281+
for (const group of affectedFormGroups) {
282+
group._removeRoutedErrorFields(fieldsToReplace)
283+
}
271284

272285
for (const { group } of formGroups) {
273286
group._cancelValidation()

packages/form-core/src/FormGroupApi/FormGroupApi.lib.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,22 @@ export class InternalFormGroupApi<
578578
this._routedErrorFields = []
579579
}
580580

581+
// TODO: Remove this targeted cleanup when routed errors migrate to the new
582+
// error state structure. Until then, replacement must drop stale field refs.
583+
_removeRoutedErrorFields(fieldsToRemove: ReadonlySet<AnyInternalFieldApi>) {
584+
for (let index = 0; index < this._routedErrorFields.length; index++) {
585+
const fieldRefs = this._routedErrorFields[index]
586+
if (!fieldRefs) continue
587+
588+
const liveFieldRefs = new Set(
589+
Array.from(fieldRefs).filter((field) => !fieldsToRemove.has(field)),
590+
)
591+
if (liveFieldRefs.size !== fieldRefs.size) {
592+
this._routedErrorFields[index] = liveFieldRefs
593+
}
594+
}
595+
}
596+
581597
_validate = async (
582598
signal: ConfigurableValidationTrigger | 'submit',
583599
opts?: { triggerFieldApi?: AnyInternalFieldApi },

packages/form-core/tests/FormGroupApi/FormGroupApi.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,43 @@ describe('FormGroupApi', () => {
388388
await vi.waitFor(() => expect(validator).toHaveBeenCalledOnce())
389389
})
390390

391+
it('drops killed routed fields while preserving live routed fields', async () => {
392+
const form = new InternalFormApi({
393+
defaultValues: { guestDetails: { name: '', email: '' } },
394+
})
395+
const nameField = form._getOrCreateFieldApi({
396+
name: 'guestDetails.name',
397+
})
398+
const emailField = form._getOrCreateFieldApi({
399+
name: 'guestDetails.email',
400+
})
401+
const group = new InternalFormGroupApi({
402+
form,
403+
name: 'guestDetails',
404+
validators: [
405+
{
406+
triggers: [],
407+
run: () => ({
408+
fields: {
409+
name: 'Name is required',
410+
email: 'Email is required',
411+
},
412+
}),
413+
},
414+
],
415+
})
416+
417+
await group.validate('submit')
418+
expect(group._routedErrorFields[0]).toEqual(
419+
new Set([nameField, emailField]),
420+
)
421+
422+
form.deleteField('guestDetails.name')
423+
424+
expect(nameField._isKilled).toBe(true)
425+
expect(group._routedErrorFields[0]).toEqual(new Set([emailField]))
426+
})
427+
391428
it('clears backing-node validation when cleanup cancels a group run', async () => {
392429
const validator = vi.fn(() => new Promise<null>(() => {}))
393430
const form = new InternalFormApi({

0 commit comments

Comments
 (0)