diff --git a/README.md b/README.md index 87b4a05..38d6507 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,12 @@ const customer = form.mutators.pop('customers') - [Mutators](#mutators) - [`form.mutators.insert(name: string, index: number, value: any) => undefined`](#formmutatorsinsertname-string-index-number-value-any--undefined) + - [`form.mutators.merge(name: string, value: Array) => void`](#formmutatorsmergename-string-value-arrayany--void) - [`form.mutators.move(name: string, from: number, to: number) => undefined`](#formmutatorsmovename-string-from-number-to-number--undefined) - [`form.mutators.pop(name: string) => any`](#formmutatorspopname-string--any) - [`form.mutators.push(name: string, value: any) => void`](#formmutatorspushname-string-value-any--void) - [`form.mutators.remove(name: string, index: number) => any`](#formmutatorsremovename-string-index-number--any) + - [`form.mutators.removeBatch(name: string, indexes: Array) => undefined`](#formmutatorsremovebatchname-string-indexes-arraynumber--undefined) - [`form.mutators.shift(name: string) => any`](#formmutatorsshiftname-string--any) - [`form.mutators.swap(name: string, indexA: number, indexB: number) => void`](#formmutatorsswapname-string-indexa-number-indexb-number--void) - [`form.mutators.update(name: string, index: number, value: any) => void`](#formmutatorsupdatename-string-index-number-value-any--void) @@ -67,6 +69,10 @@ const customer = form.mutators.pop('customers') Inserts a value into the specified index of the array field. +### `form.mutators.merge(name: string, value: Array) => void` + +Merges an array at the end of the array field. + ### `form.mutators.move(name: string, from: number, to: number) => undefined` Moves a value from one index to another index in the array field. @@ -84,6 +90,10 @@ Pushes a value onto the end of an array field. Removes a value from the specified index of the array field. Returns the removed value. +### `form.mutators.removeBatch(name: string, indexes: Array) => undefined` + +Removes the values at the specified indexes of the array field. + ### `form.mutators.shift(name: string) => any` Removes a value from the beginning of the array field. Returns the value. diff --git a/src/index.d.test.ts b/src/index.d.test.ts index 1b7d4a2..22b3e54 100644 --- a/src/index.d.test.ts +++ b/src/index.d.test.ts @@ -15,9 +15,11 @@ const form = createForm({ const mutators: Mutators = (form.mutators as any) as Mutators mutators.insert('customers', 0, { firstName: '', lastName: '' }) +mutators.merge('customers', [{ firstName: '', lastName: '' }, { firstName: '', lastName: '' }]) mutators.move('customers', 0, 1) const customer = mutators.pop('customers') mutators.push('customers', { firstName: '', lastName: '' }) +mutators.removeBatch('customers', [0]) const removed = mutators.remove('customers', 0) const shifted = mutators.shift('customers') mutators.swap('customers', 0, 1) diff --git a/src/index.d.ts b/src/index.d.ts index b3e9f3d..8c36276 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,9 +1,11 @@ import { Mutator } from 'final-form' export const insert: Mutator +export const merge: Mutator export const move: Mutator export const pop: Mutator export const push: Mutator +export const removeBatch: Mutator export const remove: Mutator export const shift: Mutator export const swap: Mutator @@ -12,9 +14,11 @@ export const unshift: Mutator export interface DefaultType { insert: Mutator + merge: Mutator move: Mutator pop: Mutator push: Mutator + removeBatch: Mutator remove: Mutator shift: Mutator swap: Mutator @@ -28,10 +32,12 @@ export default d /** The shape of the mutators once final-form has bound them to state */ export interface Mutators { insert: (name: string, index: number, value: any) => void + merge: (name: string, value: Array) => void, move: (name: string, from: number, to: number) => void pop: (name: string) => any push: (name: string, value: any) => void remove: (name: string, index: number) => any + removeBatch: (name: string, indexes: Array) => any shift: (name: string) => any swap: (name: string, indexA: number, indexB: number) => void update: (name: string, index: number, value: any) => void diff --git a/src/index.js b/src/index.js index 26fadaf..c144fd9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,12 @@ // @flow import type { Mutator } from 'final-form' import insert from './insert' +import merge from './merge' import move from './move' import pop from './pop' import push from './push' import remove from './remove' +import removeBatch from './removeBatch' import shift from './shift' import swap from './swap' import unshift from './unshift' @@ -12,10 +14,12 @@ import update from './update' const mutators: { [string]: Mutator } = { insert, + merge, move, pop, push, remove, + removeBatch, shift, swap, unshift, diff --git a/src/index.js.flow b/src/index.js.flow index 08cec50..46666c8 100644 --- a/src/index.js.flow +++ b/src/index.js.flow @@ -8,10 +8,12 @@ declare export default DefaultType /** The shape of the mutators once final-form has bound them to state */ export type Mutators = { insert: (name: string, index: number, value: any) => void, + merge: (name: string, value: Array) => void, move: (name: string, from: number, to: number) => void, pop: (name: string) => any, push: (name: string, value: any) => void, remove: (name: string, index: number) => any, + removeBatch: (name: string, indexes: Array) => any, shift: (name: string) => any, swap: (name: string, indexA: number, indexB: number) => void, update: (name: string, index: number, value: any) => void, diff --git a/src/merge.js b/src/merge.js new file mode 100644 index 0000000..f3e80bb --- /dev/null +++ b/src/merge.js @@ -0,0 +1,16 @@ +// @flow +import type { MutableState, Mutator, Tools } from 'final-form' + +const merge: Mutator = ( + [name, value]: any[], + state: MutableState, + { changeValue }: Tools +) => { + changeValue( + state, + name, + (array: ?(any[])): any[] => (array ? [...array, ...value] : value) + ) +} + +export default merge diff --git a/src/merge.test.js b/src/merge.test.js new file mode 100644 index 0000000..e1ac21a --- /dev/null +++ b/src/merge.test.js @@ -0,0 +1,35 @@ +import merge from './merge' + +describe('merge', () => { + const getOp = value => { + const changeValue = jest.fn() + merge(['foo', value], {}, { changeValue }) + return changeValue.mock.calls[0][2] + } + + it('should call changeValue once', () => { + const changeValue = jest.fn() + const state = {} + const result = merge(['foo', ['bar', 'baz']], state, { changeValue }) + expect(result).toBeUndefined() + expect(changeValue).toHaveBeenCalled() + expect(changeValue).toHaveBeenCalledTimes(1) + expect(changeValue.mock.calls[0][0]).toBe(state) + expect(changeValue.mock.calls[0][1]).toBe('foo') + expect(typeof changeValue.mock.calls[0][2]).toBe('function') + }) + + it('should turn undefined into an array with two values', () => { + const op = getOp(['bar', 'baz']) + const result = op(undefined) + expect(Array.isArray(result)).toBe(true) + expect(result).toEqual(['bar', 'baz']) + }) + + it('should merge the array at the end of the original array', () => { + const op = getOp(['d', 'e']) + const result = op(['a', 'b', 'c']) + expect(Array.isArray(result)).toBe(true) + expect(result).toEqual(['a', 'b', 'c', 'd', 'e']) + }) +}) diff --git a/src/removeBatch.js b/src/removeBatch.js new file mode 100644 index 0000000..1c487a5 --- /dev/null +++ b/src/removeBatch.js @@ -0,0 +1,36 @@ +// @flow +import type { MutableState, Mutator, Tools } from 'final-form' + +const removeBatch: Mutator = ( + [name, indexes]: any[], + state: MutableState, + { changeValue }: Tools +) => { + changeValue( + state, + name, + (array: ?(any[])): ?(any[]) => { + if (!array || !indexes) { + return array + } + + let mask = new Array(indexes.length) + for (let i = 0; i < indexes.length; i++) { + mask[indexes[i]] = true + } + + let offset = 0 + for (let i = 0; i < array.length; i++) { + if (mask[i] === undefined) { + array[offset] = array[i] + offset++ + } + } + + array.length = offset + return array + } + ) +} + +export default removeBatch diff --git a/src/removeBatch.test.js b/src/removeBatch.test.js new file mode 100644 index 0000000..0aff698 --- /dev/null +++ b/src/removeBatch.test.js @@ -0,0 +1,41 @@ +import removeBatch from './removeBatch' + +describe('merge', () => { + const getOp = value => { + const changeValue = jest.fn() + removeBatch(['foo', value], {}, { changeValue }) + return changeValue.mock.calls[0][2] + } + + it('should call changeValue once', () => { + const changeValue = jest.fn() + const state = {} + const result = removeBatch(['foo', [1, 2]], state, { changeValue }) + expect(result).toBeUndefined() + expect(changeValue).toHaveBeenCalled() + expect(changeValue).toHaveBeenCalledTimes(1) + expect(changeValue.mock.calls[0][0]).toBe(state) + expect(changeValue.mock.calls[0][1]).toBe('foo') + expect(typeof changeValue.mock.calls[0][2]).toBe('function') + }) + + it('should return undefined if array is undefined', () => { + const op = getOp([0, 1]) + const result = op(undefined) + expect(result).toBeUndefined() + }) + + it('should return the original array if no indexes are specified to be removed', () => { + const op = getOp([]) + const result = op(['a', 'b', 'c', 'd', 'e']) + expect(Array.isArray(result)).toBe(true) + expect(result).toEqual(['a', 'b', 'c', 'd', 'e']) + }) + + it('should remove the values at the specified indexes', () => { + const op = getOp([1, 3]) + const result = op(['a', 'b', 'c', 'd', 'e']) + expect(Array.isArray(result)).toBe(true) + expect(result).toEqual(['a', 'c', 'e']) + }) +})