Skip to content

Commit

Permalink
Fixed myriad bugs with moving field state (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikras authored Jul 17, 2019
1 parent 629a7c0 commit d0a198f
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 65 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.13.0",
"final-form": "^4.18.0",
"final-form": "^4.18.2",
"flow-bin": "^0.102.0",
"glow": "^1.2.2",
"husky": "^3.0.0",
Expand All @@ -73,7 +73,7 @@
"typescript": "^3.5.3"
},
"peerDependencies": {
"final-form": "^4.18.0"
"final-form": "^4.18.2"
},
"lint-staged": {
"*.{js*,ts*,json,md,css}": [
Expand Down
8 changes: 3 additions & 5 deletions src/insert.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'

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

// now we have increment any higher indexes
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
const changes = {}
const backup = { ...state.fields }
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
if (tokens) {
const fieldIndex = Number(tokens[1])
if (fieldIndex >= index) {
// inc index one higher
const incrementedKey = `${name}[${fieldIndex + 1}]${tokens[2]}`
changes[incrementedKey] = { ...state.fields[key] } // make copy of field state
changes[incrementedKey].name = incrementedKey
changes[incrementedKey].lastFieldState = undefined
moveFieldState(state, backup[key], incrementedKey)
}
if (fieldIndex === index) {
resetFieldState(key)
}
}
})
state.fields = { ...state.fields, ...changes }
}

export default insert
38 changes: 12 additions & 26 deletions src/move.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'

const move: Mutator<any> = (
[name, from, to]: any[],
Expand Down Expand Up @@ -27,43 +28,28 @@ const move: Mutator<any> = (
// decrement all indices between from and to
for (let i = from; i < to; i++) {
const destKey = `${name}[${i}]${suffix}`
moveFieldState({
destKey,
source: state.fields[`${name}[${i + 1}]${suffix}`]
})
moveFieldState(
state,
state.fields[`${name}[${i + 1}]${suffix}`],
destKey
)
}
} else {
// moving to a lower index
// increment all indices between to and from
for (let i = from; i > to; i--) {
const destKey = `${name}[${i}]${suffix}`
moveFieldState({
destKey,
source: state.fields[`${name}[${i - 1}]${suffix}`]
})
moveFieldState(
state,
state.fields[`${name}[${i - 1}]${suffix}`],
destKey
)
}
}
const toKey = `${name}[${to}]${suffix}`
moveFieldState({
destKey: toKey,
source: backup
})
moveFieldState(state, backup, toKey)
}
})

function moveFieldState({ destKey, source }) {
state.fields[destKey] = {
...source,
name: destKey,
// prevent functions from being overwritten
// if the state.fields[destKey] does not exist, it will be created
// when that field gets registered, with its own change/blur/focus callbacks
change: state.fields[destKey] && state.fields[destKey].change,
blur: state.fields[destKey] && state.fields[destKey].blur,
focus: state.fields[destKey] && state.fields[destKey].focus,
lastFieldState: undefined // clearing lastFieldState forces renotification
}
}
}

export default move
23 changes: 23 additions & 0 deletions src/moveFieldState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @flow
import type { MutableState } from 'final-form'

function moveFieldState(
state: MutableState<any>,
source: Object,
destKey: string,
oldState: MutableState<any> = state
) {
state.fields[destKey] = {
...source,
name: destKey,
// prevent functions from being overwritten
// if the state.fields[destKey] does not exist, it will be created
// when that field gets registered, with its own change/blur/focus callbacks
change: oldState.fields[destKey] && oldState.fields[destKey].change,
blur: oldState.fields[destKey] && oldState.fields[destKey].blur,
focus: oldState.fields[destKey] && oldState.fields[destKey].focus,
lastFieldState: undefined // clearing lastFieldState forces renotification
}
}

export default moveFieldState
7 changes: 3 additions & 4 deletions src/remove.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'

const remove: Mutator<any> = (
[name, index]: any[],
Expand All @@ -17,7 +18,7 @@ const remove: Mutator<any> = (
// now we have to remove any subfields for our index,
// and decrement all higher indexes.
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
const backup = { ...state.fields }
const backup = { ...state, fields: { ...state.fields } }
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
if (tokens) {
Expand All @@ -29,9 +30,7 @@ const remove: Mutator<any> = (
// shift all higher ones down
delete state.fields[key]
const decrementedKey = `${name}[${fieldIndex - 1}]${tokens[2]}`
state.fields[decrementedKey] = backup[key]
state.fields[decrementedKey].name = decrementedKey
state.fields[decrementedKey].lastFieldState = undefined
moveFieldState(state, backup.fields[key], decrementedKey, backup)
}
}
})
Expand Down
33 changes: 33 additions & 0 deletions src/remove.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ describe('remove', () => {
const after = mutate(before)
state.formState.values = setIn(state.formState.values, name, after) || {}
}
function blur0() {}
function change0() {}
function focus0() {}
function blur1() {}
function change1() {}
function focus1() {}
function blur2() {}
function change2() {}
function focus2() {}
function blur3() {}
function change3() {}
function focus3() {}
const state = {
formState: {
values: {
Expand All @@ -68,21 +80,33 @@ describe('remove', () => {
fields: {
'foo[0]': {
name: 'foo[0]',
blur: blur0,
change: change0,
focus: focus0,
touched: true,
error: 'A Error'
},
'foo[1]': {
name: 'foo[1]',
blur: blur1,
change: change1,
focus: focus1,
touched: false,
error: 'B Error'
},
'foo[2]': {
name: 'foo[2]',
blur: blur2,
change: change2,
focus: focus2,
touched: true,
error: 'C Error'
},
'foo[3]': {
name: 'foo[3]',
blur: blur3,
change: change3,
focus: focus3,
touched: false,
error: 'D Error'
},
Expand All @@ -105,17 +129,26 @@ describe('remove', () => {
fields: {
'foo[0]': {
name: 'foo[0]',
blur: blur0,
change: change0,
focus: focus0,
touched: true,
error: 'A Error'
},
'foo[1]': {
name: 'foo[1]',
blur: blur1,
change: change1,
focus: focus1,
touched: true,
error: 'C Error',
lastFieldState: undefined
},
'foo[2]': {
name: 'foo[2]',
blur: blur2,
change: change2,
focus: focus2,
touched: false,
error: 'D Error',
lastFieldState: undefined
Expand Down
11 changes: 5 additions & 6 deletions src/removeBatch.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'

const countBelow = (array, value) =>
array.reduce((count, item) => (item < value ? count + 1 : count), 0)
Expand Down Expand Up @@ -38,7 +39,7 @@ const removeBatch: Mutator<any> = (
// now we have to remove any subfields for our indexes,
// and decrement all higher indexes.
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
const newFields = {}
const newState = { ...state, fields: {} }
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
if (tokens) {
Expand All @@ -48,15 +49,13 @@ const removeBatch: Mutator<any> = (
// shift all higher ones down
const decrementedKey = `${name}[${fieldIndex -
countBelow(sortedIndexes, fieldIndex)}]${tokens[2]}`
newFields[decrementedKey] = state.fields[key]
newFields[decrementedKey].name = decrementedKey
newFields[decrementedKey].lastFieldState = undefined
moveFieldState(newState, state.fields[key], decrementedKey, state)
}
} else {
newFields[key] = state.fields[key]
newState.fields[key] = state.fields[key]
}
})
state.fields = newFields
state.fields = newState.fields
return returnValue
}

Expand Down
Loading

0 comments on commit d0a198f

Please sign in to comment.