@@ -7,6 +7,26 @@ import Field from "./Field";
77const onSubmitMock = ( ) => { } ;
88const arrayInitialValues = { items : [ { name : "a" } , { name : "b" } ] } ;
99const nestedInitialValues = { parent : { child : { value : null } } } ;
10+ const listInitialValues = {
11+ items : [
12+ { id : "a" , label : "A" } ,
13+ { id : "b" , label : "B" } ,
14+ { id : "c" , label : "C" } ,
15+ ] ,
16+ } ;
17+
18+ // Stable React key per item, positional field name per index. A row that moves
19+ // keeps its instance and only changes its `name`, so the registration effect
20+ // re-runs against a path final-form dropped during the same commit's cleanup.
21+ const renderRows = ( values ) =>
22+ values . items . map ( ( item , index ) => (
23+ < Field
24+ key = { item . id }
25+ name = { `items[${ index } ].label` }
26+ component = "input"
27+ data-testid = { `label-${ item . id } ` }
28+ />
29+ ) ) ;
1030
1131describe ( "useField issue #1095" , ( ) => {
1232 it ( "does not overwrite a value set through change() when a field first mounts at that path" , ( ) => {
@@ -316,4 +336,59 @@ describe("useField issue #1095", () => {
316336 expect ( getByTestId ( "late" ) . value ) . toBe ( "seeded" ) ;
317337 expect ( form . getState ( ) . values . late ) . toBe ( "seeded" ) ;
318338 } ) ;
339+
340+ it ( "keeps edited labels with their rows when an item is inserted at the head" , ( ) => {
341+ let form ;
342+ const { getByTestId } = render (
343+ < Form onSubmit = { onSubmitMock } initialValues = { listInitialValues } >
344+ { ( props ) => {
345+ form = props . form ;
346+ return renderRows ( props . values ) ;
347+ } }
348+ </ Form > ,
349+ ) ;
350+
351+ act ( ( ) => form . change ( "items[0].label" , "A-edited" ) ) ;
352+
353+ act ( ( ) =>
354+ form . change ( "items" , [
355+ { id : "d" , label : "D" } ,
356+ ...form . getState ( ) . values . items ,
357+ ] ) ,
358+ ) ;
359+
360+ expect ( form . getState ( ) . values . items ) . toEqual ( [
361+ { id : "d" , label : "D" } ,
362+ { id : "a" , label : "A-edited" } ,
363+ { id : "b" , label : "B" } ,
364+ { id : "c" , label : "C" } ,
365+ ] ) ;
366+ expect ( getByTestId ( "label-d" ) . value ) . toBe ( "D" ) ;
367+ expect ( getByTestId ( "label-a" ) . value ) . toBe ( "A-edited" ) ;
368+ } ) ;
369+
370+ it ( "keeps edited labels with their rows when an item is removed from the middle" , ( ) => {
371+ let form ;
372+ const { getByTestId } = render (
373+ < Form onSubmit = { onSubmitMock } initialValues = { listInitialValues } >
374+ { ( props ) => {
375+ form = props . form ;
376+ return renderRows ( props . values ) ;
377+ } }
378+ </ Form > ,
379+ ) ;
380+
381+ act ( ( ) => form . change ( "items[2].label" , "C-edited" ) ) ;
382+
383+ act ( ( ) => {
384+ const items = form . getState ( ) . values . items ;
385+ form . change ( "items" , [ items [ 0 ] , items [ 2 ] ] ) ;
386+ } ) ;
387+
388+ expect ( form . getState ( ) . values . items ) . toEqual ( [
389+ { id : "a" , label : "A" } ,
390+ { id : "c" , label : "C-edited" } ,
391+ ] ) ;
392+ expect ( getByTestId ( "label-c" ) . value ) . toBe ( "C-edited" ) ;
393+ } ) ;
319394} ) ;
0 commit comments