0
# Form Spy
1
2
FormSpy component for observing form state changes without rendering form fields, perfect for external components that need form state updates.
3
4
## Capabilities
5
6
### FormSpy Component
7
8
Component that subscribes to form state changes and renders UI based on form state without rendering actual form fields.
9
10
```typescript { .api }
11
/**
12
* Component for observing form state without rendering form fields
13
* @param props - FormSpy configuration and render props
14
* @returns React element or null
15
*/
16
const FormSpy: <FormValues = Record<string, any>>(
17
props: FormSpyProps<FormValues>
18
) => React.ReactElement;
19
20
interface FormSpyProps<FormValues = Record<string, any>>
21
extends UseFormStateParams<FormValues>,
22
RenderableProps<FormSpyRenderProps<FormValues>> {}
23
24
interface FormSpyRenderProps<FormValues = Record<string, any>>
25
extends FormState<FormValues> {
26
/** Form API instance for programmatic control */
27
form: FormApi<FormValues>;
28
}
29
30
interface UseFormStateParams<FormValues = Record<string, any>> {
31
/** Callback when form state changes */
32
onChange?: (formState: FormState<FormValues>) => void;
33
/** Form state subscription configuration */
34
subscription?: FormSubscription;
35
}
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import React from "react";
42
import { Form, Field, FormSpy } from "react-final-form";
43
44
// Basic FormSpy usage
45
function BasicFormSpy() {
46
return (
47
<Form onSubmit={(values) => console.log(values)}>
48
{({ handleSubmit }) => (
49
<form onSubmit={handleSubmit}>
50
<Field name="firstName" component="input" />
51
52
<FormSpy>
53
{({ values, dirty, invalid }) => (
54
<div>
55
<p>Form is {dirty ? "dirty" : "pristine"}</p>
56
<p>Form is {invalid ? "invalid" : "valid"}</p>
57
<pre>{JSON.stringify(values, null, 2)}</pre>
58
</div>
59
)}
60
</FormSpy>
61
</form>
62
)}
63
</Form>
64
);
65
}
66
67
// FormSpy with subscription
68
function OptimizedFormSpy() {
69
return (
70
<Form onSubmit={(values) => console.log(values)}>
71
{({ handleSubmit }) => (
72
<form onSubmit={handleSubmit}>
73
<Field name="email" component="input" type="email" />
74
75
<FormSpy subscription={{ values: true, submitting: true }}>
76
{({ values, submitting }) => (
77
<div>
78
{submitting && <p>Submitting...</p>}
79
<p>Email: {values.email}</p>
80
</div>
81
)}
82
</FormSpy>
83
</form>
84
)}
85
</Form>
86
);
87
}
88
89
// FormSpy with onChange callback
90
function CallbackFormSpy() {
91
const handleFormChange = (state: any) => {
92
console.log("Form state changed:", state);
93
// Save to localStorage, send analytics, etc.
94
};
95
96
return (
97
<Form onSubmit={(values) => console.log(values)}>
98
{({ handleSubmit }) => (
99
<form onSubmit={handleSubmit}>
100
<Field name="username" component="input" />
101
102
<FormSpy onChange={handleFormChange} />
103
</form>
104
)}
105
</Form>
106
);
107
}
108
```
109
110
### FormSpy with Form Control
111
112
FormSpy provides access to the form API for programmatic form control.
113
114
```typescript { .api }
115
/**
116
* FormSpy render props include form API for programmatic control
117
*/
118
interface FormSpyRenderProps<FormValues = Record<string, any>>
119
extends FormState<FormValues> {
120
/** Complete form API instance */
121
form: FormApi<FormValues>;
122
}
123
```
124
125
**Usage Examples:**
126
127
```typescript
128
// FormSpy with form control buttons
129
function FormControlSpy() {
130
return (
131
<Form onSubmit={(values) => console.log(values)}>
132
{({ handleSubmit }) => (
133
<form onSubmit={handleSubmit}>
134
<Field name="message" component="textarea" />
135
136
<FormSpy>
137
{({ form, pristine, invalid, values }) => (
138
<div>
139
<button
140
type="button"
141
onClick={() => form.reset()}
142
disabled={pristine}
143
>
144
Reset Form
145
</button>
146
147
<button
148
type="button"
149
onClick={() => form.change("message", "Hello World")}
150
>
151
Set Default Message
152
</button>
153
154
<button
155
type="button"
156
onClick={() => form.focus("message")}
157
>
158
Focus Message
159
</button>
160
161
<pre>{JSON.stringify(values, null, 2)}</pre>
162
</div>
163
)}
164
</FormSpy>
165
</form>
166
)}
167
</Form>
168
);
169
}
170
171
// FormSpy for conditional rendering
172
function ConditionalFormSpy() {
173
return (
174
<Form onSubmit={(values) => console.log(values)}>
175
{({ handleSubmit }) => (
176
<form onSubmit={handleSubmit}>
177
<Field name="accountType">
178
{({ input }) => (
179
<select {...input}>
180
<option value="">Select Account Type</option>
181
<option value="personal">Personal</option>
182
<option value="business">Business</option>
183
</select>
184
)}
185
</Field>
186
187
<FormSpy subscription={{ values: true }}>
188
{({ values }) => (
189
<>
190
{values.accountType === "business" && (
191
<Field name="companyName" component="input" placeholder="Company Name" />
192
)}
193
{values.accountType === "personal" && (
194
<Field name="dateOfBirth" component="input" type="date" />
195
)}
196
</>
197
)}
198
</FormSpy>
199
</form>
200
)}
201
</Form>
202
);
203
}
204
```
205
206
### FormSpy Subscription Configuration
207
208
FormSpy supports the same subscription configuration as Form components for performance optimization.
209
210
```typescript { .api }
211
/**
212
* FormSpy subscription options for optimized rendering
213
*/
214
interface FormSubscription {
215
active?: boolean;
216
dirty?: boolean;
217
dirtyFields?: boolean;
218
dirtySinceLastSubmit?: boolean;
219
error?: boolean;
220
errors?: boolean;
221
hasSubmitErrors?: boolean;
222
hasValidationErrors?: boolean;
223
initialValues?: boolean;
224
invalid?: boolean;
225
modified?: boolean;
226
modifiedSinceLastSubmit?: boolean;
227
pristine?: boolean;
228
submitError?: boolean;
229
submitErrors?: boolean;
230
submitFailed?: boolean;
231
submitSucceeded?: boolean;
232
submitting?: boolean;
233
touched?: boolean;
234
valid?: boolean;
235
validating?: boolean;
236
values?: boolean;
237
visited?: boolean;
238
}
239
```
240
241
**Usage Example:**
242
243
```typescript
244
// Optimized FormSpy with minimal subscriptions
245
function MinimalFormSpy() {
246
return (
247
<Form onSubmit={(values) => console.log(values)}>
248
{({ handleSubmit }) => (
249
<form onSubmit={handleSubmit}>
250
<Field name="search" component="input" type="search" />
251
252
{/* Only re-renders when values change */}
253
<FormSpy subscription={{ values: true }}>
254
{({ values }) => (
255
<div>
256
Search results for: {values.search}
257
</div>
258
)}
259
</FormSpy>
260
261
{/* Only re-renders when form state changes */}
262
<FormSpy subscription={{ pristine: true, invalid: true, submitting: true }}>
263
{({ pristine, invalid, submitting }) => (
264
<button type="submit" disabled={submitting || pristine || invalid}>
265
{submitting ? "Searching..." : "Search"}
266
</button>
267
)}
268
</FormSpy>
269
</form>
270
)}
271
</Form>
272
);
273
}
274
```
275
276
### External Form State Monitoring
277
278
FormSpy can be used outside of form render functions to monitor form state from parent components.
279
280
**Usage Example:**
281
282
```typescript
283
// External form state monitoring
284
function ExternalMonitor() {
285
const [formState, setFormState] = React.useState<any>(null);
286
287
return (
288
<div>
289
<div>
290
External form monitor:
291
<pre>{JSON.stringify(formState, null, 2)}</pre>
292
</div>
293
294
<Form onSubmit={(values) => console.log(values)}>
295
{({ handleSubmit }) => (
296
<form onSubmit={handleSubmit}>
297
<Field name="data" component="input" />
298
299
<FormSpy onChange={setFormState} />
300
301
<button type="submit">Submit</button>
302
</form>
303
)}
304
</Form>
305
</div>
306
);
307
}
308
```
309
310
### Performance Considerations
311
312
FormSpy is optimized for performance with subscription-based updates and minimal re-rendering.
313
314
```typescript { .api }
315
/**
316
* FormSpy performance characteristics:
317
* - Only re-renders when subscribed form state changes
318
* - Supports granular subscriptions for optimal performance
319
* - Can be used for onChange callbacks without rendering
320
* - Minimal memory footprint with automatic cleanup
321
*/
322
```
323
324
**Usage Example:**
325
326
```typescript
327
// Performance-optimized FormSpy usage
328
function PerformantFormSpy() {
329
return (
330
<Form onSubmit={(values) => console.log(values)}>
331
{({ handleSubmit }) => (
332
<form onSubmit={handleSubmit}>
333
<Field name="title" component="input" />
334
<Field name="content" component="textarea" />
335
336
{/* Separate spies for different concerns */}
337
<FormSpy subscription={{ submitting: true }}>
338
{({ submitting }) => submitting && <div>Saving...</div>}
339
</FormSpy>
340
341
<FormSpy subscription={{ values: true }}>
342
{({ values }) => (
343
<div>Character count: {(values.content || "").length}</div>
344
)}
345
</FormSpy>
346
347
{/* Silent monitoring without render */}
348
<FormSpy
349
onChange={(state) => {
350
// Auto-save draft every 30 seconds
351
if (state.dirty) {
352
console.log("Auto-saving draft...");
353
}
354
}}
355
/>
356
</form>
357
)}
358
</Form>
359
);
360
}
361
```