@@ -382,6 +382,7 @@ export interface FieldListeners<
382382 onBlur ?: FieldListenerFn < TParentData , TName , TData >
383383 onBlurDebounceMs ?: number
384384 onMount ?: FieldListenerFn < TParentData , TName , TData >
385+ onUnmount ?: FieldListenerFn < TParentData , TName , TData >
385386 onSubmit ?: FieldListenerFn < TParentData , TName , TData >
386387}
387388
@@ -1275,6 +1276,7 @@ export class FieldApi<
12751276
12761277 /**
12771278 * Mounts the field instance to the form.
1279+ * @returns A function to unmount the field instance.
12781280 */
12791281 mount = ( ) => {
12801282 if ( this . options . defaultValue !== undefined && ! this . getMeta ( ) . isTouched ) {
@@ -1322,8 +1324,86 @@ export class FieldApi<
13221324 fieldApi : this ,
13231325 } )
13241326
1325- // TODO: Remove
1326- return ( ) => { }
1327+ return ( ) => {
1328+ // Stop any in-flight async validation or listener work tied to this instance.
1329+ for ( const [ key , timeout ] of Object . entries (
1330+ this . timeoutIds . validations ,
1331+ ) ) {
1332+ if ( timeout ) {
1333+ clearTimeout ( timeout )
1334+ this . timeoutIds . validations [
1335+ key as keyof typeof this . timeoutIds . validations
1336+ ] = null
1337+ }
1338+ }
1339+ for ( const [ key , timeout ] of Object . entries ( this . timeoutIds . listeners ) ) {
1340+ if ( timeout ) {
1341+ clearTimeout ( timeout )
1342+ this . timeoutIds . listeners [
1343+ key as keyof typeof this . timeoutIds . listeners
1344+ ] = null
1345+ }
1346+ }
1347+ for ( const [ key , timeout ] of Object . entries (
1348+ this . timeoutIds . formListeners ,
1349+ ) ) {
1350+ if ( timeout ) {
1351+ clearTimeout ( timeout )
1352+ this . timeoutIds . formListeners [
1353+ key as keyof typeof this . timeoutIds . formListeners
1354+ ] = null
1355+ }
1356+ }
1357+
1358+ const fieldInfo = this . form . fieldInfo [ this . name ]
1359+ if ( ! fieldInfo ) return
1360+
1361+ // If a newer field instance has already been mounted for this name,
1362+ // avoid touching its shared validation state during teardown.
1363+ if ( fieldInfo . instance !== this ) return
1364+
1365+ for ( const [ key , validationMeta ] of Object . entries (
1366+ fieldInfo . validationMetaMap ,
1367+ ) ) {
1368+ validationMeta ?. lastAbortController . abort ( )
1369+ fieldInfo . validationMetaMap [
1370+ key as keyof typeof fieldInfo . validationMetaMap
1371+ ] = undefined
1372+ }
1373+
1374+ this . form . baseStore . setState ( ( prev ) => ( {
1375+ // Preserve interaction flags so field-level defaultValue does not
1376+ // reseed user-entered values on remount.
1377+ ...prev ,
1378+ fieldMetaBase : {
1379+ ...prev . fieldMetaBase ,
1380+ [ this . name ] : {
1381+ ...defaultFieldMeta ,
1382+ isTouched :
1383+ prev . fieldMetaBase [ this . name ] ?. isTouched ??
1384+ defaultFieldMeta . isTouched ,
1385+ isBlurred :
1386+ prev . fieldMetaBase [ this . name ] ?. isBlurred ??
1387+ defaultFieldMeta . isBlurred ,
1388+ isDirty :
1389+ prev . fieldMetaBase [ this . name ] ?. isDirty ??
1390+ defaultFieldMeta . isDirty ,
1391+ } ,
1392+ } ,
1393+ } ) )
1394+
1395+ fieldInfo . instance = null
1396+
1397+ this . options . listeners ?. onUnmount ?.( {
1398+ value : this . state . value ,
1399+ fieldApi : this ,
1400+ } )
1401+
1402+ this . form . options . listeners ?. onFieldUnmount ?.( {
1403+ formApi : this . form ,
1404+ fieldApi : this ,
1405+ } )
1406+ }
13271407 }
13281408
13291409 /**
@@ -1790,12 +1870,13 @@ export class FieldApi<
17901870 promises : Promise < ValidationError | undefined > [ ] ,
17911871 ) => {
17921872 const errorMapKey = getErrorMapKey ( validateObj . cause )
1793- const fieldValidatorMeta = field . getInfo ( ) . validationMetaMap [ errorMapKey ]
1873+ const fieldInfo = field . getInfo ( )
1874+ const fieldValidatorMeta = fieldInfo . validationMetaMap [ errorMapKey ]
17941875
17951876 fieldValidatorMeta ?. lastAbortController . abort ( )
17961877 const controller = new AbortController ( )
17971878
1798- this . getInfo ( ) . validationMetaMap [ errorMapKey ] = {
1879+ fieldInfo . validationMetaMap [ errorMapKey ] = {
17991880 lastAbortController : controller ,
18001881 }
18011882
@@ -1804,11 +1885,11 @@ export class FieldApi<
18041885 let rawError ! : ValidationError | undefined
18051886 try {
18061887 rawError = await new Promise ( ( rawResolve , rawReject ) => {
1807- if ( this . timeoutIds . validations [ validateObj . cause ] ) {
1808- clearTimeout ( this . timeoutIds . validations [ validateObj . cause ] ! )
1888+ if ( field . timeoutIds . validations [ validateObj . cause ] ) {
1889+ clearTimeout ( field . timeoutIds . validations [ validateObj . cause ] ! )
18091890 }
18101891
1811- this . timeoutIds . validations [ validateObj . cause ] = setTimeout (
1892+ field . timeoutIds . validations [ validateObj . cause ] = setTimeout (
18121893 async ( ) => {
18131894 if ( controller . signal . aborted ) return rawResolve ( undefined )
18141895 try {
@@ -1838,14 +1919,20 @@ export class FieldApi<
18381919
18391920 const fieldLevelError = normalizeError ( rawError )
18401921 const formLevelError =
1841- asyncFormValidationResults [ this . name ] ?. [ errorMapKey ]
1922+ asyncFormValidationResults [
1923+ field . name as keyof typeof asyncFormValidationResults
1924+ ] ?. [ errorMapKey ]
18421925
18431926 const { newErrorValue, newSource } =
18441927 determineFieldLevelErrorSourceAndValue ( {
18451928 formLevelError,
18461929 fieldLevelError,
18471930 } )
18481931
1932+ if ( field . getInfo ( ) . instance !== field ) {
1933+ return resolve ( undefined )
1934+ }
1935+
18491936 field . setMeta ( ( prev ) => {
18501937 return {
18511938 ...prev ,
0 commit comments