Skip to content

Commit d0a198f

Browse files
authored
Fixed myriad bugs with moving field state (#35)
1 parent 629a7c0 commit d0a198f

10 files changed

+168
-65
lines changed

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"eslint-plugin-import": "^2.16.0",
5353
"eslint-plugin-jsx-a11y": "^6.2.1",
5454
"eslint-plugin-react": "^7.13.0",
55-
"final-form": "^4.18.0",
55+
"final-form": "^4.18.2",
5656
"flow-bin": "^0.102.0",
5757
"glow": "^1.2.2",
5858
"husky": "^3.0.0",
@@ -73,7 +73,7 @@
7373
"typescript": "^3.5.3"
7474
},
7575
"peerDependencies": {
76-
"final-form": "^4.18.0"
76+
"final-form": "^4.18.2"
7777
},
7878
"lint-staged": {
7979
"*.{js*,ts*,json,md,css}": [

src/insert.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3+
import moveFieldState from './moveFieldState'
34

45
const insert: Mutator<any> = (
56
[name, index, value]: any[],
@@ -14,24 +15,21 @@ const insert: Mutator<any> = (
1415

1516
// now we have increment any higher indexes
1617
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
17-
const changes = {}
18+
const backup = { ...state.fields }
1819
Object.keys(state.fields).forEach(key => {
1920
const tokens = pattern.exec(key)
2021
if (tokens) {
2122
const fieldIndex = Number(tokens[1])
2223
if (fieldIndex >= index) {
2324
// inc index one higher
2425
const incrementedKey = `${name}[${fieldIndex + 1}]${tokens[2]}`
25-
changes[incrementedKey] = { ...state.fields[key] } // make copy of field state
26-
changes[incrementedKey].name = incrementedKey
27-
changes[incrementedKey].lastFieldState = undefined
26+
moveFieldState(state, backup[key], incrementedKey)
2827
}
2928
if (fieldIndex === index) {
3029
resetFieldState(key)
3130
}
3231
}
3332
})
34-
state.fields = { ...state.fields, ...changes }
3533
}
3634

3735
export default insert

src/move.js

+12-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3+
import moveFieldState from './moveFieldState'
34

45
const move: Mutator<any> = (
56
[name, from, to]: any[],
@@ -27,43 +28,28 @@ const move: Mutator<any> = (
2728
// decrement all indices between from and to
2829
for (let i = from; i < to; i++) {
2930
const destKey = `${name}[${i}]${suffix}`
30-
moveFieldState({
31-
destKey,
32-
source: state.fields[`${name}[${i + 1}]${suffix}`]
33-
})
31+
moveFieldState(
32+
state,
33+
state.fields[`${name}[${i + 1}]${suffix}`],
34+
destKey
35+
)
3436
}
3537
} else {
3638
// moving to a lower index
3739
// increment all indices between to and from
3840
for (let i = from; i > to; i--) {
3941
const destKey = `${name}[${i}]${suffix}`
40-
moveFieldState({
41-
destKey,
42-
source: state.fields[`${name}[${i - 1}]${suffix}`]
43-
})
42+
moveFieldState(
43+
state,
44+
state.fields[`${name}[${i - 1}]${suffix}`],
45+
destKey
46+
)
4447
}
4548
}
4649
const toKey = `${name}[${to}]${suffix}`
47-
moveFieldState({
48-
destKey: toKey,
49-
source: backup
50-
})
50+
moveFieldState(state, backup, toKey)
5151
}
5252
})
53-
54-
function moveFieldState({ destKey, source }) {
55-
state.fields[destKey] = {
56-
...source,
57-
name: destKey,
58-
// prevent functions from being overwritten
59-
// if the state.fields[destKey] does not exist, it will be created
60-
// when that field gets registered, with its own change/blur/focus callbacks
61-
change: state.fields[destKey] && state.fields[destKey].change,
62-
blur: state.fields[destKey] && state.fields[destKey].blur,
63-
focus: state.fields[destKey] && state.fields[destKey].focus,
64-
lastFieldState: undefined // clearing lastFieldState forces renotification
65-
}
66-
}
6753
}
6854

6955
export default move

src/moveFieldState.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @flow
2+
import type { MutableState } from 'final-form'
3+
4+
function moveFieldState(
5+
state: MutableState<any>,
6+
source: Object,
7+
destKey: string,
8+
oldState: MutableState<any> = state
9+
) {
10+
state.fields[destKey] = {
11+
...source,
12+
name: destKey,
13+
// prevent functions from being overwritten
14+
// if the state.fields[destKey] does not exist, it will be created
15+
// when that field gets registered, with its own change/blur/focus callbacks
16+
change: oldState.fields[destKey] && oldState.fields[destKey].change,
17+
blur: oldState.fields[destKey] && oldState.fields[destKey].blur,
18+
focus: oldState.fields[destKey] && oldState.fields[destKey].focus,
19+
lastFieldState: undefined // clearing lastFieldState forces renotification
20+
}
21+
}
22+
23+
export default moveFieldState

src/remove.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3+
import moveFieldState from './moveFieldState'
34

45
const remove: Mutator<any> = (
56
[name, index]: any[],
@@ -17,7 +18,7 @@ const remove: Mutator<any> = (
1718
// now we have to remove any subfields for our index,
1819
// and decrement all higher indexes.
1920
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
20-
const backup = { ...state.fields }
21+
const backup = { ...state, fields: { ...state.fields } }
2122
Object.keys(state.fields).forEach(key => {
2223
const tokens = pattern.exec(key)
2324
if (tokens) {
@@ -29,9 +30,7 @@ const remove: Mutator<any> = (
2930
// shift all higher ones down
3031
delete state.fields[key]
3132
const decrementedKey = `${name}[${fieldIndex - 1}]${tokens[2]}`
32-
state.fields[decrementedKey] = backup[key]
33-
state.fields[decrementedKey].name = decrementedKey
34-
state.fields[decrementedKey].lastFieldState = undefined
33+
moveFieldState(state, backup.fields[key], decrementedKey, backup)
3534
}
3635
}
3736
})

src/remove.test.js

+33
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ describe('remove', () => {
5858
const after = mutate(before)
5959
state.formState.values = setIn(state.formState.values, name, after) || {}
6060
}
61+
function blur0() {}
62+
function change0() {}
63+
function focus0() {}
64+
function blur1() {}
65+
function change1() {}
66+
function focus1() {}
67+
function blur2() {}
68+
function change2() {}
69+
function focus2() {}
70+
function blur3() {}
71+
function change3() {}
72+
function focus3() {}
6173
const state = {
6274
formState: {
6375
values: {
@@ -68,21 +80,33 @@ describe('remove', () => {
6880
fields: {
6981
'foo[0]': {
7082
name: 'foo[0]',
83+
blur: blur0,
84+
change: change0,
85+
focus: focus0,
7186
touched: true,
7287
error: 'A Error'
7388
},
7489
'foo[1]': {
7590
name: 'foo[1]',
91+
blur: blur1,
92+
change: change1,
93+
focus: focus1,
7694
touched: false,
7795
error: 'B Error'
7896
},
7997
'foo[2]': {
8098
name: 'foo[2]',
99+
blur: blur2,
100+
change: change2,
101+
focus: focus2,
81102
touched: true,
82103
error: 'C Error'
83104
},
84105
'foo[3]': {
85106
name: 'foo[3]',
107+
blur: blur3,
108+
change: change3,
109+
focus: focus3,
86110
touched: false,
87111
error: 'D Error'
88112
},
@@ -105,17 +129,26 @@ describe('remove', () => {
105129
fields: {
106130
'foo[0]': {
107131
name: 'foo[0]',
132+
blur: blur0,
133+
change: change0,
134+
focus: focus0,
108135
touched: true,
109136
error: 'A Error'
110137
},
111138
'foo[1]': {
112139
name: 'foo[1]',
140+
blur: blur1,
141+
change: change1,
142+
focus: focus1,
113143
touched: true,
114144
error: 'C Error',
115145
lastFieldState: undefined
116146
},
117147
'foo[2]': {
118148
name: 'foo[2]',
149+
blur: blur2,
150+
change: change2,
151+
focus: focus2,
119152
touched: false,
120153
error: 'D Error',
121154
lastFieldState: undefined

src/removeBatch.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3+
import moveFieldState from './moveFieldState'
34

45
const countBelow = (array, value) =>
56
array.reduce((count, item) => (item < value ? count + 1 : count), 0)
@@ -38,7 +39,7 @@ const removeBatch: Mutator<any> = (
3839
// now we have to remove any subfields for our indexes,
3940
// and decrement all higher indexes.
4041
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
41-
const newFields = {}
42+
const newState = { ...state, fields: {} }
4243
Object.keys(state.fields).forEach(key => {
4344
const tokens = pattern.exec(key)
4445
if (tokens) {
@@ -48,15 +49,13 @@ const removeBatch: Mutator<any> = (
4849
// shift all higher ones down
4950
const decrementedKey = `${name}[${fieldIndex -
5051
countBelow(sortedIndexes, fieldIndex)}]${tokens[2]}`
51-
newFields[decrementedKey] = state.fields[key]
52-
newFields[decrementedKey].name = decrementedKey
53-
newFields[decrementedKey].lastFieldState = undefined
52+
moveFieldState(newState, state.fields[key], decrementedKey, state)
5453
}
5554
} else {
56-
newFields[key] = state.fields[key]
55+
newState.fields[key] = state.fields[key]
5756
}
5857
})
59-
state.fields = newFields
58+
state.fields = newState.fields
6059
return returnValue
6160
}
6261

0 commit comments

Comments
 (0)