Skip to content

Commit 85c4a79

Browse files
TiuSherikras
authored andcommitted
Add promises support (#17)
* Add support for promises * Add promises doc
1 parent 68ada5a commit 85c4a79

File tree

5 files changed

+164
-6
lines changed

5 files changed

+164
-6
lines changed

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ const decorator = createDecorator(
5151
(allValues.items || []).reduce((sum, value) => sum + value, 0)
5252
}
5353
},
54+
{
55+
field: 'foo', // when the value of foo changes...
56+
updates: {
57+
// ...asynchronously set field "doubleFoo" to twice the value using a promise
58+
doubleFoo: (fooValue, allValues) =>
59+
new Promise(resolve => {
60+
setTimeout(() => resolve(fooValue * 2), 100)
61+
})
62+
}
63+
},
5464
{
5565
field: /\.timeFrom/, // when a deeper field matching this pattern changes...
5666
updates: (value, name, allValues) => {
@@ -123,10 +133,10 @@ A pattern to match a field with.
123133

124134
Either an object of updater functions or a function that generates updates for multiple fields.
125135

126-
### `UpdatesByName: { [FieldName]: (value: any, allValues: Object) => any }`
136+
### `UpdatesByName: { [FieldName]: (value: any, allValues: Object) => Promise | any }`
127137

128138
Updater functions for each calculated field.
129139

130-
### `UpdatesForAll: (value: any, field: string, allValues: Object) => { [FieldName]: any }`
140+
### `UpdatesForAll: (value: any, field: string, allValues: Object) => Promise | { [FieldName]: any }`
131141

132142
Takes the value and name of the field that just changed, as well as all the values, and returns an object of fields and new values.

src/decorator.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { Decorator, FormApi } from 'final-form'
33
import type { Calculation, Updates } from './types'
44
import { getIn } from 'final-form'
5+
import isPromise from './isPromise'
56

67
const tripleEquals = (a: any, b: any) => a === b
78
const createDecorator = (...calculations: Calculation[]): Decorator => (
@@ -21,13 +22,30 @@ const createDecorator = (...calculations: Calculation[]): Decorator => (
2122
if (!isEqual(next, previous)) {
2223
if (typeof updates === 'function') {
2324
const results = updates(next, field, values)
24-
Object.keys(results).forEach(destField => {
25-
form.change(destField, results[destField])
26-
})
25+
26+
if (isPromise(results)) {
27+
results.then(resolved => {
28+
Object.keys(resolved).forEach(destField => {
29+
form.change(destField, resolved[destField])
30+
})
31+
})
32+
} else {
33+
Object.keys(results).forEach(destField => {
34+
form.change(destField, results[destField])
35+
})
36+
}
2737
} else {
2838
Object.keys(updates).forEach(destField => {
2939
const update = updates[destField]
30-
form.change(destField, update(next, values))
40+
const result = update(next, values)
41+
42+
if (isPromise(result)) {
43+
result.then(resolved => {
44+
form.change(destField, resolved)
45+
})
46+
} else {
47+
form.change(destField, result)
48+
}
3149
})
3250
}
3351
}

src/decorator.test.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,96 @@ describe('decorator', () => {
132132
expect(bar.mock.calls[1][0].value).toBe('bazbar')
133133
})
134134

135+
it('should update one field when another changes, using promises', () => {
136+
const form = createForm({ onSubmit: onSubmitMock })
137+
const spy = jest.fn()
138+
const foo = jest.fn()
139+
const bar = jest.fn()
140+
const promise = Promise.resolve('bar')
141+
form.subscribe(spy, { values: true })
142+
form.registerField('foo', foo, { value: true })
143+
form.registerField('bar', bar, { value: true })
144+
const decorator = createDecorator({
145+
field: 'foo',
146+
updates: {
147+
bar: fooValue => promise
148+
}
149+
})
150+
const unsubscribe = decorator(form)
151+
expect(typeof unsubscribe).toBe('function')
152+
153+
expect(spy).toHaveBeenCalled()
154+
expect(spy).toHaveBeenCalledTimes(1)
155+
expect(spy.mock.calls[0][0].values).toEqual({})
156+
157+
expect(foo).toHaveBeenCalled()
158+
expect(foo).toHaveBeenCalledTimes(1)
159+
expect(foo.mock.calls[0][0].value).toBeUndefined()
160+
161+
expect(bar).toHaveBeenCalled()
162+
expect(bar).toHaveBeenCalledTimes(1)
163+
expect(bar.mock.calls[0][0].value).toBeUndefined()
164+
165+
// change foo (should trigger calculation on bar)
166+
form.change('foo', 'baz')
167+
168+
return promise.then(() => {
169+
expect(spy).toHaveBeenCalledTimes(3)
170+
expect(spy.mock.calls[1][0].values).toEqual({ foo: 'baz' })
171+
expect(spy.mock.calls[2][0].values).toEqual({ foo: 'baz', bar: 'bar' })
172+
173+
expect(foo).toHaveBeenCalledTimes(2)
174+
expect(foo.mock.calls[1][0].value).toBe('baz')
175+
176+
expect(bar).toHaveBeenCalledTimes(2)
177+
expect(bar.mock.calls[1][0].value).toBe('bar')
178+
})
179+
})
180+
181+
it('should update one field when another changes, using a single promise', () => {
182+
const form = createForm({ onSubmit: onSubmitMock })
183+
const spy = jest.fn()
184+
const foo = jest.fn()
185+
const bar = jest.fn()
186+
const promise = Promise.resolve({ bar: 'bar' })
187+
form.subscribe(spy, { values: true })
188+
form.registerField('foo', foo, { value: true })
189+
form.registerField('bar', bar, { value: true })
190+
const decorator = createDecorator({
191+
field: 'foo',
192+
updates: () => promise
193+
})
194+
const unsubscribe = decorator(form)
195+
expect(typeof unsubscribe).toBe('function')
196+
197+
expect(spy).toHaveBeenCalled()
198+
expect(spy).toHaveBeenCalledTimes(1)
199+
expect(spy.mock.calls[0][0].values).toEqual({})
200+
201+
expect(foo).toHaveBeenCalled()
202+
expect(foo).toHaveBeenCalledTimes(1)
203+
expect(foo.mock.calls[0][0].value).toBeUndefined()
204+
205+
expect(bar).toHaveBeenCalled()
206+
expect(bar).toHaveBeenCalledTimes(1)
207+
expect(bar.mock.calls[0][0].value).toBeUndefined()
208+
209+
// change foo (should trigger calculation on bar)
210+
form.change('foo', 'baz')
211+
212+
return promise.then(() => {
213+
expect(spy).toHaveBeenCalledTimes(3)
214+
expect(spy.mock.calls[1][0].values).toEqual({ foo: 'baz' })
215+
expect(spy.mock.calls[2][0].values).toEqual({ foo: 'baz', bar: 'bar' })
216+
217+
expect(foo).toHaveBeenCalledTimes(2)
218+
expect(foo.mock.calls[1][0].value).toBe('baz')
219+
220+
expect(bar).toHaveBeenCalledTimes(2)
221+
expect(bar.mock.calls[1][0].value).toBe('bar')
222+
})
223+
})
224+
135225
it('should cease when unsubscribed', () => {
136226
const form = createForm({ onSubmit: onSubmitMock })
137227
const spy = jest.fn()

src/isPromise.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default obj =>
2+
!!obj &&
3+
(typeof obj === 'object' || typeof obj === 'function') &&
4+
typeof obj.then === 'function'

src/isPromise.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import isPromise from './isPromise'
2+
3+
var promise = { then: () => {} }
4+
5+
describe('calling isPromise', () => {
6+
it('should return true with a promise', () => {
7+
expect(isPromise(promise)).toBe(true)
8+
})
9+
it('returns false with null', () => {
10+
expect(isPromise(null)).toBe(false)
11+
})
12+
it('returns false with undefined', () => {
13+
expect(isPromise(undefined)).toBe(false)
14+
})
15+
it('returns false with a number', () => {
16+
expect(isPromise(0)).toBe(false)
17+
expect(isPromise(-42)).toBe(false)
18+
expect(isPromise(42)).toBe(false)
19+
})
20+
it('returns false with a string', () => {
21+
expect(isPromise('')).toBe(false)
22+
expect(isPromise('then')).toBe(false)
23+
})
24+
it('returns false with a boolean', () => {
25+
expect(isPromise(false)).toBe(false)
26+
expect(isPromise(true)).toBe(false)
27+
})
28+
it('returns false with an object', () => {
29+
expect(isPromise({})).toBe(false)
30+
expect(isPromise({ then: true })).toBe(false)
31+
})
32+
it('returns false with an array', () => {
33+
expect(isPromise([])).toBe(false)
34+
expect(isPromise([true])).toBe(false)
35+
})
36+
})

0 commit comments

Comments
 (0)