Skip to content

Commit 9cdb1c7

Browse files
authored
Added a custom isEqual function (#10)
1 parent 857771e commit 9cdb1c7

File tree

6 files changed

+105
-20
lines changed

6 files changed

+105
-20
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ A function that takes a set of calculations and returns a 🏁 Final Form
109109

110110
## Types
111111

112-
### `Calculation: { field: FieldPattern, updates: Updates }`
112+
### `Calculation: { field: FieldPattern, isEqual?: (any, any) => boolean, updates: Updates }`
113113

114-
A calculation to perform
114+
A calculation to perform, with an optional `isEqual` predicate to determine if a value has really changed (defaults to `===`).
115115

116116
### `FieldName: string`
117117

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
{
22
"name": "final-form-calculate",
33
"version": "1.0.2",
4-
"description": "Decorator for calculating field values based on other field values in 🏁 Final Form",
4+
"description":
5+
"Decorator for calculating field values based on other field values in 🏁 Final Form",
56
"main": "dist/final-form-calculate.cjs.js",
67
"jsnext:main": "dist/final-form-calculate.es.js",
78
"module": "dist/final-form-calculate.es.js",
8-
"files": [
9-
"dist"
10-
],
9+
"files": ["dist"],
1110
"scripts": {
1211
"start": "nps",
1312
"test": "nps test",
1413
"precommit": "lint-staged && npm start validate"
1514
},
16-
"author": "Erik Rasmussen <[email protected]> (http://github.com/erikras)",
15+
"author":
16+
"Erik Rasmussen <[email protected]> (http://github.com/erikras)",
1717
"license": "MIT",
1818
"repository": {
1919
"type": "git",
@@ -62,10 +62,7 @@
6262
"final-form": ">=1.3.0"
6363
},
6464
"lint-staged": {
65-
"*.{js,json,md,css}": [
66-
"prettier --write",
67-
"git add"
68-
]
65+
"*.{js,json,md,css}": ["prettier --write", "git add"]
6966
},
7067
"bundlesize": [
7168
{

src/decorator.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@ import type { Decorator, FormApi } from 'final-form'
33
import type { Calculation, Updates } from './types'
44
import { getIn } from 'final-form'
55

6+
const tripleEquals = (a: any, b: any) => a === b
67
const createDecorator = (...calculations: Calculation[]): Decorator => (
78
form: FormApi
89
) => {
910
let previousValues = {}
1011
const unsubscribe = form.subscribe(
1112
({ values }) => {
1213
form.batch(() => {
13-
const runUpdates = (field: string, updates: Updates) => {
14+
const runUpdates = (
15+
field: string,
16+
isEqual: (any, any) => boolean,
17+
updates: Updates
18+
) => {
1419
const next = values && getIn(values, field)
1520
const previous = previousValues && getIn(previousValues, field)
16-
if (next !== previous) {
21+
if (!isEqual(next, previous)) {
1722
if (typeof updates === 'function') {
1823
const results = updates(next, field, values)
1924
Object.keys(results).forEach(destField => {
@@ -28,15 +33,15 @@ const createDecorator = (...calculations: Calculation[]): Decorator => (
2833
}
2934
}
3035
const fields = form.getRegisteredFields()
31-
calculations.forEach(({ field, updates }) => {
36+
calculations.forEach(({ field, isEqual, updates }) => {
3237
if (typeof field === 'string') {
33-
runUpdates(field, updates)
38+
runUpdates(field, isEqual || tripleEquals, updates)
3439
} else {
3540
// field is a regex
3641
const regex = (field: RegExp)
3742
fields.forEach(fieldName => {
3843
if (regex.test(fieldName)) {
39-
runUpdates(fieldName, updates)
44+
runUpdates(fieldName, isEqual || tripleEquals, updates)
4045
}
4146
})
4247
}

src/decorator.test.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,86 @@ describe('decorator', () => {
355355
expect(spy.mock.calls[4][0].values).toEqual({ minimum: 3, maximum: 2 })
356356
expect(spy.mock.calls[5][0].values).toEqual({ minimum: 2, maximum: 2 })
357357
})
358+
359+
it('should allow alternate isEqual function', () => {
360+
const form = createForm({ onSubmit: onSubmitMock })
361+
const spy = jest.fn()
362+
const foo = jest.fn()
363+
const bar = jest.fn()
364+
form.subscribe(spy, { values: true })
365+
form.registerField('foo', foo, { value: true })
366+
form.registerField('bar', bar, { value: true })
367+
const decorator = createDecorator({
368+
field: 'foo',
369+
isEqual: (a, b) =>
370+
(a === undefined ? undefined : a.id) ===
371+
(b === undefined ? undefined : b.id),
372+
updates: {
373+
bar: fooValue => ({ id: fooValue.id + 1, name: `${fooValue.name}bar` })
374+
}
375+
})
376+
const unsubscribe = decorator(form)
377+
expect(typeof unsubscribe).toBe('function')
378+
379+
expect(spy).toHaveBeenCalled()
380+
expect(spy).toHaveBeenCalledTimes(1)
381+
expect(spy.mock.calls[0][0].values).toEqual({})
382+
383+
expect(foo).toHaveBeenCalled()
384+
expect(foo).toHaveBeenCalledTimes(1)
385+
expect(foo.mock.calls[0][0].value).toBeUndefined()
386+
387+
expect(bar).toHaveBeenCalled()
388+
expect(bar).toHaveBeenCalledTimes(1)
389+
expect(bar.mock.calls[0][0].value).toBeUndefined()
390+
391+
// change foo (should trigger calculation on bar)
392+
form.change('foo', { id: 1, name: 'baz' })
393+
394+
expect(spy).toHaveBeenCalledTimes(3)
395+
expect(spy.mock.calls[1][0].values).toEqual({ foo: { id: 1, name: 'baz' } })
396+
expect(spy.mock.calls[2][0].values).toEqual({
397+
foo: { id: 1, name: 'baz' },
398+
bar: { id: 2, name: 'bazbar' }
399+
})
400+
401+
expect(foo).toHaveBeenCalledTimes(2)
402+
expect(foo.mock.calls[1][0].value).toEqual({ id: 1, name: 'baz' })
403+
404+
expect(bar).toHaveBeenCalledTimes(2)
405+
expect(bar.mock.calls[1][0].value).toEqual({ id: 2, name: 'bazbar' })
406+
407+
// change foo's name, but not id
408+
form.change('foo', { id: 1, name: 'superbaz' })
409+
410+
expect(spy).toHaveBeenCalledTimes(4)
411+
expect(spy.mock.calls[3][0].values).toEqual({
412+
foo: { id: 1, name: 'superbaz' },
413+
bar: { id: 2, name: 'bazbar' }
414+
})
415+
416+
expect(foo).toHaveBeenCalledTimes(3)
417+
expect(foo.mock.calls[2][0].value).toEqual({ id: 1, name: 'superbaz' })
418+
419+
expect(bar).toHaveBeenCalledTimes(2)
420+
421+
// change foo's id: should trigger calculation
422+
form.change('foo', { id: 3, name: 'superbaz' })
423+
424+
expect(spy).toHaveBeenCalledTimes(6)
425+
expect(spy.mock.calls[4][0].values).toEqual({
426+
foo: { id: 3, name: 'superbaz' },
427+
bar: { id: 2, name: 'bazbar' }
428+
})
429+
expect(spy.mock.calls[5][0].values).toEqual({
430+
foo: { id: 3, name: 'superbaz' },
431+
bar: { id: 4, name: 'superbazbar' }
432+
})
433+
434+
expect(foo).toHaveBeenCalledTimes(4)
435+
expect(foo.mock.calls[3][0].value).toEqual({ id: 3, name: 'superbaz' })
436+
437+
expect(bar).toHaveBeenCalledTimes(3)
438+
expect(bar.mock.calls[2][0].value).toEqual({ id: 4, name: 'superbazbar' })
439+
})
358440
})

src/types.js.flow

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export type Updates = UpdatesByName | UpdatesForAll
1313

1414
export type Calculation = {
1515
field: FieldPattern,
16+
isEqual?: (any, any) => boolean,
1617
updates: Updates
1718
}

0 commit comments

Comments
 (0)