@@ -966,4 +966,107 @@ describe('FieldArray', () => {
966966 expect ( 'touched' in metaSnapshot ) . toBe ( true )
967967 expect ( 'valid' in metaSnapshot ) . toBe ( true )
968968 } )
969+
970+ it ( 'should preserve data property in field state after remove' , async ( ) => {
971+ // Reproduces: https://github.com/final-form/react-final-form-arrays/issues/165
972+ // form.getFieldState is corrupted after arrays.remove - data is lost for shifted fields
973+ let formRef : any = null
974+
975+ const { getByText } = render (
976+ < Form
977+ onSubmit = { onSubmitMock }
978+ mutators = { {
979+ ...arrayMutators ,
980+ setFieldData : ( [ name , data ] : [ string , any ] , state : any ) => {
981+ if ( state . fields [ name ] ) {
982+ state . fields [ name ] . data = data
983+ }
984+ }
985+ } }
986+ initialValues = { { customers : [ 'Alice' , 'Bob' ] } }
987+ >
988+ { ( { form } ) => {
989+ formRef = form
990+ return (
991+ < form >
992+ < FieldArray name = "customers" >
993+ { ( { fields } ) =>
994+ fields . map ( ( name , index ) => (
995+ < div key = { index } >
996+ < Field name = { name } component = "input" />
997+ < button
998+ type = "button"
999+ onClick = { ( ) => fields . remove ( index ) }
1000+ >
1001+ Remove { index }
1002+ </ button >
1003+ </ div >
1004+ ) )
1005+ }
1006+ </ FieldArray >
1007+ </ form >
1008+ )
1009+ } }
1010+ </ Form >
1011+ )
1012+
1013+ // Set data on both fields
1014+ act ( ( ) => {
1015+ formRef . mutators . setFieldData ( 'customers[0]' , { disabled : true } )
1016+ formRef . mutators . setFieldData ( 'customers[1]' , { disabled : true } )
1017+ } )
1018+
1019+ // Remove element at index 0
1020+ act ( ( ) => {
1021+ fireEvent . click ( getByText ( 'Remove 0' ) )
1022+ } )
1023+
1024+ // After removal, customers[1] becomes customers[0]
1025+ // Its data property must be preserved — not undefined or {}
1026+ const fieldState = formRef . getFieldState ( 'customers[0]' )
1027+ expect ( fieldState ) . toBeDefined ( )
1028+ expect ( fieldState ! . data ) . toEqual ( { disabled : true } )
1029+ } )
1030+
1031+ it ( 'should return defined field state (not undefined) for shifted field immediately after remove' , ( ) => {
1032+ // Edge case: getFieldState should return the field state, not undefined,
1033+ // even when called synchronously after remove (before React re-renders).
1034+ // copyField sets lastFieldState: undefined to force re-notification,
1035+ // but getFieldState(name) returns field.lastFieldState — which would be undefined.
1036+ let formRef : any = null
1037+ let removeRef : any = null
1038+
1039+ render (
1040+ < Form
1041+ onSubmit = { onSubmitMock }
1042+ mutators = { arrayMutators }
1043+ initialValues = { { items : [ 'a' , 'b' ] } }
1044+ >
1045+ { ( { form } ) => {
1046+ formRef = form
1047+ return (
1048+ < form >
1049+ < FieldArray name = "items" >
1050+ { ( { fields } ) => {
1051+ removeRef = fields . remove
1052+ return fields . map ( ( name , index ) => (
1053+ < Field key = { index } name = { name } component = "input" />
1054+ ) )
1055+ } }
1056+ </ FieldArray >
1057+ </ form >
1058+ )
1059+ } }
1060+ </ Form >
1061+ )
1062+
1063+ act ( ( ) => {
1064+ removeRef ( 0 )
1065+ } )
1066+
1067+ // After remove + React re-render cycle, getFieldState should be defined
1068+ const fieldState = formRef . getFieldState ( 'items[0]' )
1069+ expect ( fieldState ) . toBeDefined ( )
1070+ expect ( fieldState ! . value ) . toBe ( 'b' )
1071+ } )
9691072} )
0 commit comments