Skip to content

Commit 4350d28

Browse files
carlhueffmeiererikras
authored andcommitted
Fix move/swap breaking input fields (#16)
During move and swap, the field state of the source field was copied to the target field. The field state, however, contained functions that had a reference to the previous position. This caused changes to be applied to the wrong field. Resolves: #15 See also: #10
1 parent d531bb6 commit 4350d28

File tree

4 files changed

+145
-25
lines changed

4 files changed

+145
-25
lines changed

src/move.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,32 +31,40 @@ const move: Mutator = (
3131
// decrement all indices between from and to
3232
for (let i = from; i < to; i++) {
3333
const destKey = `${name}[${i}]${suffix}`
34-
state.fields[destKey] = {
35-
...state.fields[`${name}[${i + 1}]${suffix}`],
36-
name: destKey,
37-
lastFieldState: undefined // clearing lastFieldState forces renotification
38-
}
34+
moveFieldState({
35+
destKey,
36+
source: state.fields[`${name}[${i + 1}]${suffix}`]
37+
})
3938
}
4039
} else {
4140
// moving to a lower index
4241
// increment all indices between to and from
4342
for (let i = from; i > to; i--) {
4443
const destKey = `${name}[${i}]${suffix}`
45-
state.fields[destKey] = {
46-
...state.fields[`${name}[${i - 1}]${suffix}`],
47-
name: destKey,
48-
lastFieldState: undefined // clearing lastFieldState forces renotification
49-
}
44+
moveFieldState({
45+
destKey,
46+
source: state.fields[`${name}[${i - 1}]${suffix}`]
47+
})
5048
}
5149
}
5250
const toKey = `${name}[${to}]${suffix}`
53-
state.fields[toKey] = {
54-
...backup,
55-
name: toKey,
56-
lastFieldState: undefined // clearing lastFieldState forces renotification
57-
}
51+
moveFieldState({
52+
destKey: toKey,
53+
source: backup
54+
})
5855
}
5956
})
57+
58+
function moveFieldState({ destKey, source }) {
59+
state.fields[destKey] = {
60+
...source,
61+
name: destKey,
62+
change: state.fields[destKey].change, // prevent functions from being overwritten
63+
blur: state.fields[destKey].blur,
64+
focus: state.fields[destKey].focus,
65+
lastFieldState: undefined // clearing lastFieldState forces renotification
66+
}
67+
}
6068
}
6169

6270
export default move

src/move.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,4 +432,55 @@ describe('move', () => {
432432
}
433433
})
434434
})
435+
436+
it('should preserve functions in field state', () => {
437+
// implementation of changeValue taken directly from Final Form
438+
const changeValue = (state, name, mutate) => {
439+
const before = getIn(state.formState.values, name)
440+
const after = mutate(before)
441+
state.formState.values = setIn(state.formState.values, name, after) || {}
442+
}
443+
const state = {
444+
formState: {
445+
values: {
446+
foo: ['apple', 'banana', 'carrot', 'date']
447+
}
448+
},
449+
fields: {
450+
'foo[0]': {
451+
name: 'foo[0]',
452+
touched: true,
453+
error: 'Error A',
454+
lastFieldState: 'anything',
455+
change: () => 'foo[0]'
456+
},
457+
'foo[1]': {
458+
name: 'foo[1]',
459+
touched: true,
460+
error: 'Error B',
461+
lastFieldState: 'anything',
462+
change: () => 'foo[1]'
463+
},
464+
'foo[2]': {
465+
name: 'foo[2]',
466+
touched: false,
467+
error: 'Error C',
468+
lastFieldState: 'anything',
469+
change: () => 'foo[2]'
470+
},
471+
'foo[3]': {
472+
name: 'foo[3]',
473+
touched: false,
474+
error: 'Error D',
475+
lastFieldState: 'anything',
476+
change: () => 'foo[3]'
477+
}
478+
}
479+
}
480+
move(['foo', 0, 2], state, { changeValue })
481+
expect(state.fields['foo[0]'].change()).toBe('foo[0]')
482+
expect(state.fields['foo[1]'].change()).toBe('foo[1]')
483+
expect(state.fields['foo[2]'].change()).toBe('foo[2]')
484+
expect(state.fields['foo[3]'].change()).toBe('foo[3]')
485+
})
435486
})

src/swap.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,28 @@ const swap: Mutator = (
2929
const aKey = aPrefix + suffix
3030
const bKey = bPrefix + suffix
3131
const fieldA = state.fields[aKey]
32-
state.fields[aKey] = {
33-
...state.fields[bKey],
34-
name: aKey,
35-
lastFieldState: undefined // clearing lastFieldState forces renotification
36-
}
37-
state.fields[bKey] = {
38-
...fieldA,
39-
name: bKey,
40-
lastFieldState: undefined // clearing lastFieldState forces renotification
41-
}
32+
33+
moveFieldState({
34+
destKey: aKey,
35+
source: state.fields[bKey]
36+
})
37+
moveFieldState({
38+
destKey: bKey,
39+
source: fieldA
40+
})
4241
}
4342
})
43+
44+
function moveFieldState({ destKey, source }) {
45+
state.fields[destKey] = {
46+
...source,
47+
name: destKey,
48+
change: state.fields[destKey].change, // prevent functions from being overwritten
49+
blur: state.fields[destKey].blur,
50+
focus: state.fields[destKey].focus,
51+
lastFieldState: undefined // clearing lastFieldState forces renotification
52+
}
53+
}
4454
}
4555

4656
export default swap

src/swap.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,55 @@ describe('swap', () => {
254254
}
255255
})
256256
})
257+
258+
it('should preserve functions in field state', () => {
259+
// implementation of changeValue taken directly from Final Form
260+
const changeValue = (state, name, mutate) => {
261+
const before = getIn(state.formState.values, name)
262+
const after = mutate(before)
263+
state.formState.values = setIn(state.formState.values, name, after) || {}
264+
}
265+
const state = {
266+
formState: {
267+
values: {
268+
foo: ['apple', 'banana', 'carrot', 'date']
269+
}
270+
},
271+
fields: {
272+
'foo[0]': {
273+
name: 'foo[0]',
274+
touched: true,
275+
error: 'Error A',
276+
lastFieldState: 'anything',
277+
change: () => 'foo[0]'
278+
},
279+
'foo[1]': {
280+
name: 'foo[1]',
281+
touched: true,
282+
error: 'Error B',
283+
lastFieldState: 'anything',
284+
change: () => 'foo[1]'
285+
},
286+
'foo[2]': {
287+
name: 'foo[2]',
288+
touched: false,
289+
error: 'Error C',
290+
lastFieldState: 'anything',
291+
change: () => 'foo[2]'
292+
},
293+
'foo[3]': {
294+
name: 'foo[3]',
295+
touched: false,
296+
error: 'Error D',
297+
lastFieldState: 'anything',
298+
change: () => 'foo[3]'
299+
}
300+
}
301+
}
302+
swap(['foo', 0, 2], state, { changeValue })
303+
expect(state.fields['foo[0]'].change()).toBe('foo[0]')
304+
expect(state.fields['foo[1]'].change()).toBe('foo[1]')
305+
expect(state.fields['foo[2]'].change()).toBe('foo[2]')
306+
expect(state.fields['foo[3]'].change()).toBe('foo[3]')
307+
})
257308
})

0 commit comments

Comments
 (0)