Skip to content

Fix and optimize all mutators #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/copyField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @flow
import type { InternalFieldState } from 'final-form/dist/types'

function copyField(
oldFields: { [string]: InternalFieldState },
oldKey: string,
newFields: { [string]: InternalFieldState },
newKey: string
) {
newFields[newKey] = {
...oldFields[oldKey],
name: newKey,
// prevent functions from being overwritten
// if the newFields[newKey] does not exist, it will be created
// when that field gets registered, with its own change/blur/focus callbacks
change: oldFields[newKey] && oldFields[newKey].change,
blur: oldFields[newKey] && oldFields[newKey].blur,
focus: oldFields[newKey] && oldFields[newKey].focus,
lastFieldState: undefined // clearing lastFieldState forces renotification
}

if (!newFields[newKey].change) {
delete newFields[newKey].change
}

if (!newFields[newKey].blur) {
delete newFields[newKey].blur
}

if (!newFields[newKey].focus) {
delete newFields[newKey].focus
}
}

export default copyField
40 changes: 20 additions & 20 deletions src/insert.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'
import copyField from './copyField'
import { escapeRegexTokens } from './utils'

const insert: Mutator<any> = (
[name, index, value]: any[],
state: MutableState<any>,
{ changeValue, resetFieldState }: Tools<any>
{ changeValue }: Tools<any>
) => {
changeValue(state, name, (array: ?(any[])): any[] => {
const copy = [...(array || [])]
copy.splice(index, 0, value)
return copy
})

const backup = { ...state.fields }

// now we have increment any higher indexes
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)

// we need to increment high indices first so
// lower indices won't overlap
Object.keys(state.fields)
.sort()
.reverse()
.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]}`
moveFieldState(state, backup[key], incrementedKey)
}
const newFields = {}
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
if (tokens) {
const fieldIndex = Number(tokens[1])
if (fieldIndex >= index) {
// Shift all higher indices up
const incrementedKey = `${name}[${fieldIndex + 1}]${tokens[2]}`
copyField(state.fields, key, newFields, incrementedKey)
return
}
})
}

// Keep this field that does not match the name,
// or has index smaller than what is being inserted
newFields[key] = state.fields[key]
})

state.fields = newFields
}

export default insert
41 changes: 27 additions & 14 deletions src/insert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('insert', () => {
})

it('should increment other field data from the specified index', () => {
const array = ['a', 'b', 'c', 'd']
const array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']
// implementation of changeValue taken directly from Final Form
const changeValue = (state, name, mutate) => {
const before = getIn(state.formState.values, name)
Expand All @@ -111,15 +111,15 @@ describe('insert', () => {
touched: true,
error: 'B Error'
},
'foo[2]': {
name: 'foo[2]',
'foo[9]': {
name: 'foo[9]',
touched: true,
error: 'C Error'
error: 'J Error'
},
'foo[3]': {
name: 'foo[3]',
'foo[10]': {
name: 'foo[10]',
touched: false,
error: 'D Error'
error: 'K Error'
}
}
}
Expand All @@ -132,7 +132,20 @@ describe('insert', () => {
expect(state).toEqual({
formState: {
values: {
foo: ['a', 'NEWVALUE', 'b', 'c', 'd']
foo: [
'a',
'NEWVALUE',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k'
]
}
},
fields: {
Expand All @@ -147,16 +160,16 @@ describe('insert', () => {
error: 'B Error',
lastFieldState: undefined
},
'foo[3]': {
name: 'foo[3]',
'foo[10]': {
name: 'foo[10]',
touched: true,
error: 'C Error',
error: 'J Error',
lastFieldState: undefined
},
'foo[4]': {
name: 'foo[4]',
'foo[11]': {
name: 'foo[11]',
touched: false,
error: 'D Error',
error: 'K Error',
lastFieldState: undefined
}
}
Expand Down
64 changes: 36 additions & 28 deletions src/move.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFields from './moveFields'
import restoreFunctions from './restoreFunctions'

const TMP: string = 'tmp'
import copyField from './copyField'
import { escapeRegexTokens } from './utils'

const move: Mutator<any> = (
[name, from, to]: any[],
Expand All @@ -21,34 +19,44 @@ const move: Mutator<any> = (
return copy
})

//make a copy of a state for further functions restore
const backupState = { ...state, fields: { ...state.fields } }

// move this row to tmp index
const fromPrefix = `${name}[${from}]`
moveFields(name, fromPrefix, TMP, state)

if (from < to) {
// moving to a higher index
// decrement all indices between from and to
for (let i = from + 1; i <= to; i++) {
const innerFromPrefix = `${name}[${i}]`
moveFields(name, innerFromPrefix, `${i - 1}`, state)
}
const newFields = {}
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)
let lowest
let highest
let increment
if (from > to) {
lowest = to
highest = from
increment = 1
} else {
// moving to a lower index
// increment all indices between to and from
for (let i = from - 1; i >= to; i--) {
const innerFromPrefix = `${name}[${i}]`
moveFields(name, innerFromPrefix, `${i + 1}`, state)
}
lowest = from
highest = to
increment = -1
}
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
if (tokens) {
const fieldIndex = Number(tokens[1])
if (fieldIndex === from) {
const newKey = `${name}[${to}]${tokens[2]}`
copyField(state.fields, key, newFields, newKey)
return
}

if (lowest <= fieldIndex && fieldIndex <= highest) {
// Shift all indices
const newKey = `${name}[${fieldIndex + increment}]${tokens[2]}`
copyField(state.fields, key, newFields, newKey)
return
}
}

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

restoreFunctions(state, backupState)
state.fields = newFields
}

export default move
33 changes: 0 additions & 33 deletions src/moveFieldState.js

This file was deleted.

20 changes: 0 additions & 20 deletions src/moveFields.js

This file was deleted.

34 changes: 7 additions & 27 deletions src/pop.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import { escapeRegexTokens } from './utils'
import remove from './remove'

const pop: Mutator<any> = (
[name]: any[],
state: MutableState<any>,
{ changeValue }: Tools<any>
tools: Tools<any>
) => {
let result
let removedIndex: ?number
changeValue(state, name, (array: ?(any[])): ?(any[]) => {
if (array) {
if (!array.length) {
return []
}
removedIndex = array.length - 1
result = array[removedIndex]
return array.slice(0, removedIndex)
}
})

// now we have to remove any subfields for our index,
if (removedIndex !== undefined) {
const pattern = new RegExp(
`^${escapeRegexTokens(name)}\\[${removedIndex}].*`
)
Object.keys(state.fields).forEach(key => {
if (pattern.test(key)) {
delete state.fields[key]
}
})
}
return result
const { getIn } = tools;
const array = getIn(state.formState.values, name)
return array && array.length > 0
? remove([name, array.length - 1], state, tools)
: undefined
}

export default pop
Loading