Skip to content

Commit a9be6b5

Browse files
authored
Merge pull request #61 from huan086/fix/mutators
Fix and optimize all mutators
2 parents 4a65647 + 71bebc7 commit a9be6b5

16 files changed

+408
-439
lines changed

src/copyField.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @flow
2+
import type { InternalFieldState } from 'final-form/dist/types'
3+
4+
function copyField(
5+
oldFields: { [string]: InternalFieldState },
6+
oldKey: string,
7+
newFields: { [string]: InternalFieldState },
8+
newKey: string
9+
) {
10+
newFields[newKey] = {
11+
...oldFields[oldKey],
12+
name: newKey,
13+
// prevent functions from being overwritten
14+
// if the newFields[newKey] does not exist, it will be created
15+
// when that field gets registered, with its own change/blur/focus callbacks
16+
change: oldFields[newKey] && oldFields[newKey].change,
17+
blur: oldFields[newKey] && oldFields[newKey].blur,
18+
focus: oldFields[newKey] && oldFields[newKey].focus,
19+
lastFieldState: undefined // clearing lastFieldState forces renotification
20+
}
21+
22+
if (!newFields[newKey].change) {
23+
delete newFields[newKey].change
24+
}
25+
26+
if (!newFields[newKey].blur) {
27+
delete newFields[newKey].blur
28+
}
29+
30+
if (!newFields[newKey].focus) {
31+
delete newFields[newKey].focus
32+
}
33+
}
34+
35+
export default copyField

src/insert.js

+20-20
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3-
import moveFieldState from './moveFieldState'
3+
import copyField from './copyField'
44
import { escapeRegexTokens } from './utils'
55

66
const insert: Mutator<any> = (
77
[name, index, value]: any[],
88
state: MutableState<any>,
9-
{ changeValue, resetFieldState }: Tools<any>
9+
{ changeValue }: Tools<any>
1010
) => {
1111
changeValue(state, name, (array: ?(any[])): any[] => {
1212
const copy = [...(array || [])]
1313
copy.splice(index, 0, value)
1414
return copy
1515
})
1616

17-
const backup = { ...state.fields }
18-
1917
// now we have increment any higher indexes
2018
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)
21-
22-
// we need to increment high indices first so
23-
// lower indices won't overlap
24-
Object.keys(state.fields)
25-
.sort()
26-
.reverse()
27-
.forEach(key => {
28-
const tokens = pattern.exec(key)
29-
if (tokens) {
30-
const fieldIndex = Number(tokens[1])
31-
if (fieldIndex >= index) {
32-
// inc index one higher
33-
const incrementedKey = `${name}[${fieldIndex + 1}]${tokens[2]}`
34-
moveFieldState(state, backup[key], incrementedKey)
35-
}
19+
const newFields = {}
20+
Object.keys(state.fields).forEach(key => {
21+
const tokens = pattern.exec(key)
22+
if (tokens) {
23+
const fieldIndex = Number(tokens[1])
24+
if (fieldIndex >= index) {
25+
// Shift all higher indices up
26+
const incrementedKey = `${name}[${fieldIndex + 1}]${tokens[2]}`
27+
copyField(state.fields, key, newFields, incrementedKey)
28+
return
3629
}
37-
})
30+
}
31+
32+
// Keep this field that does not match the name,
33+
// or has index smaller than what is being inserted
34+
newFields[key] = state.fields[key]
35+
})
36+
37+
state.fields = newFields
3838
}
3939

4040
export default insert

src/insert.test.js

+27-14
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('insert', () => {
8484
})
8585

8686
it('should increment other field data from the specified index', () => {
87-
const array = ['a', 'b', 'c', 'd']
87+
const array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']
8888
// implementation of changeValue taken directly from Final Form
8989
const changeValue = (state, name, mutate) => {
9090
const before = getIn(state.formState.values, name)
@@ -111,15 +111,15 @@ describe('insert', () => {
111111
touched: true,
112112
error: 'B Error'
113113
},
114-
'foo[2]': {
115-
name: 'foo[2]',
114+
'foo[9]': {
115+
name: 'foo[9]',
116116
touched: true,
117-
error: 'C Error'
117+
error: 'J Error'
118118
},
119-
'foo[3]': {
120-
name: 'foo[3]',
119+
'foo[10]': {
120+
name: 'foo[10]',
121121
touched: false,
122-
error: 'D Error'
122+
error: 'K Error'
123123
}
124124
}
125125
}
@@ -132,7 +132,20 @@ describe('insert', () => {
132132
expect(state).toEqual({
133133
formState: {
134134
values: {
135-
foo: ['a', 'NEWVALUE', 'b', 'c', 'd']
135+
foo: [
136+
'a',
137+
'NEWVALUE',
138+
'b',
139+
'c',
140+
'd',
141+
'e',
142+
'f',
143+
'g',
144+
'h',
145+
'i',
146+
'j',
147+
'k'
148+
]
136149
}
137150
},
138151
fields: {
@@ -147,16 +160,16 @@ describe('insert', () => {
147160
error: 'B Error',
148161
lastFieldState: undefined
149162
},
150-
'foo[3]': {
151-
name: 'foo[3]',
163+
'foo[10]': {
164+
name: 'foo[10]',
152165
touched: true,
153-
error: 'C Error',
166+
error: 'J Error',
154167
lastFieldState: undefined
155168
},
156-
'foo[4]': {
157-
name: 'foo[4]',
169+
'foo[11]': {
170+
name: 'foo[11]',
158171
touched: false,
159-
error: 'D Error',
172+
error: 'K Error',
160173
lastFieldState: undefined
161174
}
162175
}

src/move.js

+36-28
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3-
import moveFields from './moveFields'
4-
import restoreFunctions from './restoreFunctions'
5-
6-
const TMP: string = 'tmp'
3+
import copyField from './copyField'
4+
import { escapeRegexTokens } from './utils'
75

86
const move: Mutator<any> = (
97
[name, from, to]: any[],
@@ -21,34 +19,44 @@ const move: Mutator<any> = (
2119
return copy
2220
})
2321

24-
//make a copy of a state for further functions restore
25-
const backupState = { ...state, fields: { ...state.fields } }
26-
27-
// move this row to tmp index
28-
const fromPrefix = `${name}[${from}]`
29-
moveFields(name, fromPrefix, TMP, state)
30-
31-
if (from < to) {
32-
// moving to a higher index
33-
// decrement all indices between from and to
34-
for (let i = from + 1; i <= to; i++) {
35-
const innerFromPrefix = `${name}[${i}]`
36-
moveFields(name, innerFromPrefix, `${i - 1}`, state)
37-
}
22+
const newFields = {}
23+
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)
24+
let lowest
25+
let highest
26+
let increment
27+
if (from > to) {
28+
lowest = to
29+
highest = from
30+
increment = 1
3831
} else {
39-
// moving to a lower index
40-
// increment all indices between to and from
41-
for (let i = from - 1; i >= to; i--) {
42-
const innerFromPrefix = `${name}[${i}]`
43-
moveFields(name, innerFromPrefix, `${i + 1}`, state)
44-
}
32+
lowest = from
33+
highest = to
34+
increment = -1
4535
}
36+
Object.keys(state.fields).forEach(key => {
37+
const tokens = pattern.exec(key)
38+
if (tokens) {
39+
const fieldIndex = Number(tokens[1])
40+
if (fieldIndex === from) {
41+
const newKey = `${name}[${to}]${tokens[2]}`
42+
copyField(state.fields, key, newFields, newKey)
43+
return
44+
}
45+
46+
if (lowest <= fieldIndex && fieldIndex <= highest) {
47+
// Shift all indices
48+
const newKey = `${name}[${fieldIndex + increment}]${tokens[2]}`
49+
copyField(state.fields, key, newFields, newKey)
50+
return
51+
}
52+
}
4653

47-
// move from tmp index to destination
48-
const tmpPrefix = `${name}[${TMP}]`
49-
moveFields(name, tmpPrefix, to, state)
54+
// Keep this field that does not match the name,
55+
// or has index smaller or larger than affected range
56+
newFields[key] = state.fields[key]
57+
})
5058

51-
restoreFunctions(state, backupState)
59+
state.fields = newFields
5260
}
5361

5462
export default move

src/moveFieldState.js

-33
This file was deleted.

src/moveFields.js

-20
This file was deleted.

src/pop.js

+7-27
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,17 @@
11
// @flow
22
import type { MutableState, Mutator, Tools } from 'final-form'
3-
import { escapeRegexTokens } from './utils'
3+
import remove from './remove'
44

55
const pop: Mutator<any> = (
66
[name]: any[],
77
state: MutableState<any>,
8-
{ changeValue }: Tools<any>
8+
tools: Tools<any>
99
) => {
10-
let result
11-
let removedIndex: ?number
12-
changeValue(state, name, (array: ?(any[])): ?(any[]) => {
13-
if (array) {
14-
if (!array.length) {
15-
return []
16-
}
17-
removedIndex = array.length - 1
18-
result = array[removedIndex]
19-
return array.slice(0, removedIndex)
20-
}
21-
})
22-
23-
// now we have to remove any subfields for our index,
24-
if (removedIndex !== undefined) {
25-
const pattern = new RegExp(
26-
`^${escapeRegexTokens(name)}\\[${removedIndex}].*`
27-
)
28-
Object.keys(state.fields).forEach(key => {
29-
if (pattern.test(key)) {
30-
delete state.fields[key]
31-
}
32-
})
33-
}
34-
return result
10+
const { getIn } = tools;
11+
const array = getIn(state.formState.values, name)
12+
return array && array.length > 0
13+
? remove([name, array.length - 1], state, tools)
14+
: undefined
3515
}
3616

3717
export default pop

0 commit comments

Comments
 (0)