Skip to content

Commit a19235a

Browse files
authored
Provided Form API to FormSpy (#105)
* Provided form api to FormSpy * Formatted readme
1 parent 36583b7 commit a19235a

File tree

5 files changed

+151
-45
lines changed

5 files changed

+151
-45
lines changed

README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
✅ Opt-in subscriptions - only update on the state you need!
1717

18-
✅ 💥 **2.7k gzipped** 💥
18+
✅ 💥 **2.8k gzipped** 💥
1919

2020
---
2121

@@ -194,6 +194,13 @@ const MyForm = () => (
194194
* [`render?: (props: FormSpyRenderProps) => React.Node`](#render-props-formspyrenderprops--reactnode)
195195
* [`subscription?: FormSubscription`](#subscription-formsubscription-1)
196196
* [`FormSpyRenderProps`](#formspyrenderprops)
197+
* [`batch: (fn: () => void) => void)`](#batch-fn---void--void-1)
198+
* [`blur: (name: string) => void`](#blur-name-string--void-1)
199+
* [`change: (name: string, value: any) => void`](#change-name-string-value-any--void-1)
200+
* [`focus: (name: string) => void`](#focus-name-string--void-1)
201+
* [`initialize: (values: Object) => void`](#initialize-values-object--void-1)
202+
* [`mutators?: { [string]: Function }`](#mutators--string-function--1)
203+
* [`reset: () => void`](#reset---void-1)
197204

198205
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
199206

@@ -657,4 +664,35 @@ function or component. These props are of type
657664
that the values you receive here are dependent upon which values of
658665
[`FormState`](https://github.com/final-form/final-form#formstate) you have
659666
subscribed to with the
660-
[`subscription` prop](https://github.com/final-form/react-final-form#subscription-formsubscription).
667+
[`subscription` prop](https://github.com/final-form/react-final-form#subscription-formsubscription). Also included will be many of the same props provided to [`FormRenderProps`](#formrenderprops):
668+
669+
#### `batch: (fn: () => void) => void)`
670+
671+
A function that allows batch updates to be done to the form state.
672+
[See the 🏁 Final Form docs on `batch`](https://github.com/final-form/final-form#batch-fn---void--void).
673+
674+
#### `blur: (name: string) => void`
675+
676+
A function to blur (mark inactive) any field.
677+
678+
#### `change: (name: string, value: any) => void`
679+
680+
A function to change the value of any field.
681+
682+
#### `focus: (name: string) => void`
683+
684+
A function to focus (mark active) any field.
685+
686+
#### `initialize: (values: Object) => void`
687+
688+
A function that initializes the form values.
689+
[See the 🏁 Final Form docs on `initialize`](https://github.com/final-form/final-form#initialize-values-object--void).
690+
691+
#### `mutators?: { [string]: Function }`
692+
693+
[See the 🏁 Final Form docs on `mutators`](https://github.com/final-form/final-form#mutators--string-function-).
694+
695+
#### `reset: () => void`
696+
697+
A function that resets the form values to their last initialized values.
698+
[See the 🏁 Final Form docs on `reset`](https://github.com/final-form/final-form#reset---void).

src/FormSpy.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,23 @@ export default class FormSpy extends React.PureComponent<Props, State> {
8080

8181
render() {
8282
const { onChange, subscription, ...rest } = this.props
83+
const { reactFinalForm } = this.context
8384
return onChange
8485
? null
85-
: renderComponent({ ...rest, ...this.state.state }, 'FormSpy')
86+
: renderComponent(
87+
{
88+
...rest,
89+
...this.state.state,
90+
mutators: reactFinalForm && reactFinalForm.mutators,
91+
batch: reactFinalForm && reactFinalForm.batch,
92+
blur: reactFinalForm && reactFinalForm.blur,
93+
change: reactFinalForm && reactFinalForm.change,
94+
focus: reactFinalForm && reactFinalForm.focus,
95+
initialize: reactFinalForm && reactFinalForm.initialize,
96+
reset: reactFinalForm && reactFinalForm.reset
97+
},
98+
'FormSpy'
99+
)
86100
}
87101
}
88102

src/FormSpy.test.js

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import Field from './Field'
55
import FormSpy from './FormSpy'
66

77
const onSubmitMock = values => {}
8+
const hasFormApi = props => {
9+
expect(typeof props.batch).toBe('function')
10+
expect(typeof props.blur).toBe('function')
11+
expect(typeof props.change).toBe('function')
12+
expect(typeof props.focus).toBe('function')
13+
expect(typeof props.initialize).toBe('function')
14+
expect(typeof props.reset).toBe('function')
15+
}
816

917
describe('FormSpy', () => {
1018
it('should warn error if not used inside a form', () => {
@@ -26,37 +34,35 @@ describe('FormSpy', () => {
2634
)
2735
expect(render).toHaveBeenCalled()
2836
expect(render).toHaveBeenCalledTimes(1)
29-
expect(render).toHaveBeenCalledWith({
30-
dirty: false,
31-
errors: {},
32-
invalid: false,
33-
pristine: true,
34-
submitFailed: false,
35-
submitSucceeded: false,
36-
submitting: false,
37-
valid: true,
38-
validating: false,
39-
values: {}
40-
})
37+
hasFormApi(render.mock.calls[0][0])
38+
expect(render.mock.calls[0][0].dirty).toBe(false)
39+
expect(render.mock.calls[0][0].errors).toEqual({})
40+
expect(render.mock.calls[0][0].invalid).toBe(false)
41+
expect(render.mock.calls[0][0].pristine).toBe(true)
42+
expect(render.mock.calls[0][0].submitFailed).toBe(false)
43+
expect(render.mock.calls[0][0].submitSucceeded).toBe(false)
44+
expect(render.mock.calls[0][0].submitting).toBe(false)
45+
expect(render.mock.calls[0][0].valid).toBe(true)
46+
expect(render.mock.calls[0][0].validating).toBe(false)
47+
expect(render.mock.calls[0][0].values).toEqual({})
4148
expect(renderInput).toHaveBeenCalled()
4249
expect(renderInput).toHaveBeenCalledTimes(1)
4350

4451
// change value
4552
renderInput.mock.calls[0][0].input.onChange('bar')
4653

4754
expect(render).toHaveBeenCalledTimes(2)
48-
expect(render).toHaveBeenCalledWith({
49-
dirty: true,
50-
errors: {},
51-
invalid: false,
52-
pristine: false,
53-
submitFailed: false,
54-
submitSucceeded: false,
55-
submitting: false,
56-
valid: true,
57-
validating: false,
58-
values: { foo: 'bar' }
59-
})
55+
hasFormApi(render.mock.calls[1][0])
56+
expect(render.mock.calls[1][0].dirty).toBe(true)
57+
expect(render.mock.calls[1][0].errors).toEqual({})
58+
expect(render.mock.calls[1][0].invalid).toBe(false)
59+
expect(render.mock.calls[1][0].pristine).toBe(false)
60+
expect(render.mock.calls[1][0].submitFailed).toBe(false)
61+
expect(render.mock.calls[1][0].submitSucceeded).toBe(false)
62+
expect(render.mock.calls[1][0].submitting).toBe(false)
63+
expect(render.mock.calls[1][0].valid).toBe(true)
64+
expect(render.mock.calls[1][0].validating).toBe(false)
65+
expect(render.mock.calls[1][0].values).toEqual({ foo: 'bar' })
6066
expect(renderInput).toHaveBeenCalledTimes(2)
6167
})
6268

@@ -94,16 +100,36 @@ describe('FormSpy', () => {
94100
const dom = TestUtils.renderIntoDocument(<Container />)
95101
expect(render).toHaveBeenCalled()
96102
expect(render).toHaveBeenCalledTimes(1)
97-
expect(render).toHaveBeenCalledWith({
98-
values: { dog: 'Odie', cat: 'Garfield' },
99-
pristine: true
103+
hasFormApi(render.mock.calls[0][0])
104+
expect(render.mock.calls[0][0].dirty).toBeUndefined()
105+
expect(render.mock.calls[0][0].errors).toBeUndefined()
106+
expect(render.mock.calls[0][0].invalid).toBeUndefined()
107+
expect(render.mock.calls[0][0].pristine).toBe(true)
108+
expect(render.mock.calls[0][0].submitFailed).toBeUndefined()
109+
expect(render.mock.calls[0][0].submitSucceeded).toBeUndefined()
110+
expect(render.mock.calls[0][0].submitting).toBeUndefined()
111+
expect(render.mock.calls[0][0].valid).toBeUndefined()
112+
expect(render.mock.calls[0][0].validating).toBeUndefined()
113+
expect(render.mock.calls[0][0].values).toEqual({
114+
dog: 'Odie',
115+
cat: 'Garfield'
100116
})
101117

102118
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
103119
TestUtils.Simulate.click(button)
104120

105121
expect(render).toHaveBeenCalledTimes(2)
106-
expect(render).toHaveBeenCalledWith({ dirty: false, submitting: false })
122+
hasFormApi(render.mock.calls[1][0])
123+
expect(render.mock.calls[1][0].dirty).toBe(false)
124+
expect(render.mock.calls[1][0].errors).toBeUndefined()
125+
expect(render.mock.calls[1][0].invalid).toBeUndefined()
126+
expect(render.mock.calls[1][0].pristine).toBeUndefined()
127+
expect(render.mock.calls[1][0].submitFailed).toBeUndefined()
128+
expect(render.mock.calls[1][0].submitSucceeded).toBeUndefined()
129+
expect(render.mock.calls[1][0].submitting).toBe(false)
130+
expect(render.mock.calls[1][0].valid).toBeUndefined()
131+
expect(render.mock.calls[1][0].validating).toBeUndefined()
132+
expect(render.mock.calls[1][0].values).toBeUndefined()
107133
})
108134

109135
it('should hear changes', () => {
@@ -124,10 +150,17 @@ describe('FormSpy', () => {
124150
)
125151
expect(render).toHaveBeenCalled()
126152
expect(render).toHaveBeenCalledTimes(1)
127-
expect(render).toHaveBeenCalledWith({
128-
values: {},
129-
dirty: false
130-
})
153+
hasFormApi(render.mock.calls[0][0])
154+
expect(render.mock.calls[0][0].dirty).toBe(false)
155+
expect(render.mock.calls[0][0].errors).toBeUndefined()
156+
expect(render.mock.calls[0][0].invalid).toBeUndefined()
157+
expect(render.mock.calls[0][0].pristine).toBeUndefined()
158+
expect(render.mock.calls[0][0].submitFailed).toBeUndefined()
159+
expect(render.mock.calls[0][0].submitSucceeded).toBeUndefined()
160+
expect(render.mock.calls[0][0].submitting).toBeUndefined()
161+
expect(render.mock.calls[0][0].valid).toBeUndefined()
162+
expect(render.mock.calls[0][0].validating).toBeUndefined()
163+
expect(render.mock.calls[0][0].values).toEqual({})
131164
expect(renderInput).toHaveBeenCalled()
132165
expect(renderInput).toHaveBeenCalledTimes(1)
133166

@@ -136,10 +169,17 @@ describe('FormSpy', () => {
136169

137170
// once because whole form rerendered, and again because state changed
138171
expect(render).toHaveBeenCalledTimes(3)
139-
expect(render).toHaveBeenCalledWith({
140-
values: { foo: 'bar' },
141-
dirty: true
142-
})
172+
hasFormApi(render.mock.calls[2][0])
173+
expect(render.mock.calls[2][0].dirty).toBe(true)
174+
expect(render.mock.calls[2][0].errors).toBeUndefined()
175+
expect(render.mock.calls[2][0].invalid).toBeUndefined()
176+
expect(render.mock.calls[2][0].pristine).toBeUndefined()
177+
expect(render.mock.calls[2][0].submitFailed).toBeUndefined()
178+
expect(render.mock.calls[2][0].submitSucceeded).toBeUndefined()
179+
expect(render.mock.calls[2][0].submitting).toBeUndefined()
180+
expect(render.mock.calls[2][0].valid).toBeUndefined()
181+
expect(render.mock.calls[2][0].validating).toBeUndefined()
182+
expect(render.mock.calls[2][0].values).toEqual({ foo: 'bar' })
143183
expect(renderInput).toHaveBeenCalledTimes(2)
144184
})
145185

src/index.d.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,17 @@ export interface FieldRenderProps {
3737
}>
3838
}
3939

40-
export interface FormRenderProps extends FormState {
40+
export interface SubsetFormApi {
41+
batch: (fn: () => void) => void
42+
blur: (name: string) => void
43+
change: (name: string, value: any) => void
44+
focus: (name: string) => void
45+
initialize: (values: object) => void
46+
mutators?: { [key: string]: Function }
47+
reset: () => void
48+
}
49+
50+
export interface FormRenderProps extends FormState, SubsetFormApi {
4151
batch: (fn: () => void) => void
4252
blur: (name: string) => void
4353
change: (name: string, value: any) => void
@@ -48,7 +58,7 @@ export interface FormRenderProps extends FormState {
4858
reset: () => void
4959
}
5060

51-
export type FormSpyRenderProps = FormState
61+
export interface FormSpyRenderProps extends FormState, SubsetFormApi {}
5262

5363
export interface RenderableProps<T> {
5464
children?: ((props: T) => React.ReactNode) | React.ReactNode
@@ -61,7 +71,6 @@ export interface FormProps extends Config, RenderableProps<FormRenderProps> {
6171
decorators?: Decorator[]
6272
}
6373

64-
6574
export interface FieldProps extends RenderableProps<FieldRenderProps> {
6675
allowNull?: boolean
6776
format?: ((value: any, name: string) => any) | null

src/types.js.flow

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,21 @@ export type FieldRenderProps = {
3838
}>
3939
}
4040

41-
export type FormRenderProps = {
41+
export type SubsetFormApi = {
4242
blur: (name: string) => void,
4343
change: (name: string, value: any) => void,
4444
focus: (name: string) => void,
45-
handleSubmit: (SyntheticEvent<HTMLFormElement>) => void,
4645
initialize: (values: Object) => void,
46+
mutators: { [string]: Function },
4747
reset: () => void
4848
} & FormState
4949

50-
export type FormSpyRenderProps = FormState
50+
export type FormRenderProps = {
51+
handleSubmit: (SyntheticEvent<HTMLFormElement>) => void
52+
} & FormState &
53+
SubsetFormApi
54+
55+
export type FormSpyRenderProps = SubsetFormApi & FormState
5156

5257
export type RenderableProps<T> = $Shape<{
5358
children: ((props: T) => React.Node) | React.Node,

0 commit comments

Comments
 (0)