0
# Advanced Form Patterns
1
2
Advanced features for creating custom form hooks with component injection, React contexts for form and field data, higher-order components, and utility functions for form manipulation and helper operations.
3
4
## Capabilities
5
6
### createFormHook
7
8
Creates a custom form hook with injected field and form components.
9
10
```typescript { .api }
11
/**
12
* Creates a custom form hook with extended field and form components
13
* Enables component injection pattern for reusable form UI components
14
*
15
* @param props.fieldComponents - Custom field-level components to inject
16
* @param props.fieldContext - React context for field data
17
* @param props.formContext - React context for form data
18
* @param props.formComponents - Custom form-level components to inject
19
* @returns Object containing useAppForm, withForm, and withFieldGroup functions
20
*/
21
function createFormHook<
22
const TComponents extends Record<string, ComponentType<any>>,
23
const TFormComponents extends Record<string, ComponentType<any>>,
24
>({
25
fieldComponents,
26
fieldContext,
27
formContext,
28
formComponents,
29
}: CreateFormHookProps<TComponents, TFormComponents>): {
30
/**
31
* Custom form hook with injected components
32
* Similar to useForm but returns AppFieldExtendedReactFormApi with custom components
33
*/
34
useAppForm: <
35
TFormData,
36
TOnMount extends undefined | FormValidateOrFn<TFormData> = undefined,
37
TOnChange extends undefined | FormValidateOrFn<TFormData> = undefined,
38
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
39
TOnBlur extends undefined | FormValidateOrFn<TFormData> = undefined,
40
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
41
TOnSubmit extends undefined | FormValidateOrFn<TFormData> = undefined,
42
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
43
TOnDynamic extends undefined | FormValidateOrFn<TFormData> = undefined,
44
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
45
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
46
TSubmitMeta = never,
47
>(
48
props: FormOptions<
49
TFormData,
50
TOnMount,
51
TOnChange,
52
TOnChangeAsync,
53
TOnBlur,
54
TOnBlurAsync,
55
TOnSubmit,
56
TOnSubmitAsync,
57
TOnDynamic,
58
TOnDynamicAsync,
59
TOnServer,
60
TSubmitMeta
61
>,
62
) => AppFieldExtendedReactFormApi<
63
TFormData,
64
TOnMount,
65
TOnChange,
66
TOnChangeAsync,
67
TOnBlur,
68
TOnBlurAsync,
69
TOnSubmit,
70
TOnSubmitAsync,
71
TOnDynamic,
72
TOnDynamicAsync,
73
TOnServer,
74
TSubmitMeta,
75
TComponents,
76
TFormComponents
77
>;
78
79
/**
80
* Higher-order component for wrapping forms
81
* Provides form instance to render function
82
*/
83
withForm: <
84
TFormData,
85
TOnMount extends undefined | FormValidateOrFn<TFormData> = undefined,
86
TOnChange extends undefined | FormValidateOrFn<TFormData> = undefined,
87
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
88
TOnBlur extends undefined | FormValidateOrFn<TFormData> = undefined,
89
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
90
TOnSubmit extends undefined | FormValidateOrFn<TFormData> = undefined,
91
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
92
TOnDynamic extends undefined | FormValidateOrFn<TFormData> = undefined,
93
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
94
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
95
TSubmitMeta = never,
96
TRenderProps extends object = {},
97
>(
98
props: WithFormProps<
99
TFormData,
100
TOnMount,
101
TOnChange,
102
TOnChangeAsync,
103
TOnBlur,
104
TOnBlurAsync,
105
TOnSubmit,
106
TOnSubmitAsync,
107
TOnDynamic,
108
TOnDynamicAsync,
109
TOnServer,
110
TSubmitMeta,
111
TComponents,
112
TFormComponents,
113
TRenderProps
114
>,
115
) => (props: PropsWithChildren<TRenderProps>) => JSX.Element;
116
117
/**
118
* Higher-order component for wrapping field groups
119
* Provides field group instance to render function
120
*/
121
withFieldGroup: <
122
TFieldGroupData,
123
TSubmitMeta = never,
124
TRenderProps extends Record<string, unknown> = {},
125
>(
126
props: WithFieldGroupProps<
127
TFieldGroupData,
128
TComponents,
129
TFormComponents,
130
TSubmitMeta,
131
TRenderProps
132
>,
133
) => <
134
TFormData,
135
TFields extends
136
| DeepKeysOfType<TFormData, TFieldGroupData | null | undefined>
137
| FieldsMap<TFormData, TFieldGroupData>,
138
TOnMount extends undefined | FormValidateOrFn<TFormData> = undefined,
139
TOnChange extends undefined | FormValidateOrFn<TFormData> = undefined,
140
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
141
TOnBlur extends undefined | FormValidateOrFn<TFormData> = undefined,
142
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
143
TOnSubmit extends undefined | FormValidateOrFn<TFormData> = undefined,
144
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
145
TOnDynamic extends undefined | FormValidateOrFn<TFormData> = undefined,
146
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
147
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData> = undefined,
148
>(
149
params: PropsWithChildren<
150
TRenderProps & {
151
form: AppFieldExtendedReactFormApi<...>;
152
fields: TFields;
153
}
154
>,
155
) => JSX.Element;
156
};
157
158
interface CreateFormHookProps<
159
TFieldComponents extends Record<string, ComponentType<any>>,
160
TFormComponents extends Record<string, ComponentType<any>>,
161
> {
162
/** Custom field-level components (e.g., custom input wrappers) */
163
fieldComponents: TFieldComponents;
164
165
/** React context for accessing field data */
166
fieldContext: Context<AnyFieldApi>;
167
168
/** Custom form-level components (e.g., custom form wrapper) */
169
formComponents: TFormComponents;
170
171
/** React context for accessing form data */
172
formContext: Context<AnyFormApi>;
173
}
174
```
175
176
### createFormHookContexts
177
178
Creates React contexts and hooks for accessing form and field data.
179
180
```typescript { .api }
181
/**
182
* Creates React contexts and hooks for form and field data access
183
* Use this to create contexts before calling createFormHook
184
*
185
* @returns Object containing contexts and hook functions
186
*/
187
function createFormHookContexts(): {
188
/** React context for field data */
189
fieldContext: Context<AnyFieldApi>;
190
191
/**
192
* Hook to access field context within a field component
193
* @returns Current field API instance
194
* @throws Error if called outside of field component context
195
*/
196
useFieldContext: <TData>() => FieldApi<
197
any,
198
string,
199
TData,
200
any,
201
any,
202
any,
203
any,
204
any,
205
any,
206
any,
207
any,
208
any,
209
any,
210
any,
211
any,
212
any,
213
any,
214
any,
215
any,
216
any,
217
any,
218
any,
219
any
220
>;
221
222
/**
223
* Hook to access form context within a form component
224
* @returns Current form API instance
225
* @throws Error if called outside of form component context
226
*/
227
useFormContext: () => ReactFormExtendedApi<
228
Record<string, never>,
229
any,
230
any,
231
any,
232
any,
233
any,
234
any,
235
any,
236
any,
237
any,
238
any,
239
any
240
>;
241
242
/** React context for form data */
243
formContext: Context<AnyFormApi>;
244
};
245
```
246
247
### WithFormProps
248
249
Props interface for withForm higher-order component.
250
251
```typescript { .api }
252
interface WithFormProps<
253
TFormData,
254
TOnMount extends undefined | FormValidateOrFn<TFormData>,
255
TOnChange extends undefined | FormValidateOrFn<TFormData>,
256
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
257
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
258
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
259
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
260
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
261
TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
262
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
263
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
264
TSubmitMeta,
265
TFieldComponents extends Record<string, ComponentType<any>>,
266
TFormComponents extends Record<string, ComponentType<any>>,
267
TRenderProps extends object = Record<string, never>,
268
> extends FormOptions<
269
TFormData,
270
TOnMount,
271
TOnChange,
272
TOnChangeAsync,
273
TOnBlur,
274
TOnBlurAsync,
275
TOnSubmit,
276
TOnSubmitAsync,
277
TOnDynamic,
278
TOnDynamicAsync,
279
TOnServer,
280
TSubmitMeta
281
> {
282
/** Optional additional props for render function */
283
props?: TRenderProps;
284
285
/**
286
* Render function that receives form instance and props
287
* @param props - Combined props with form API
288
* @returns JSX element
289
*/
290
render: (
291
props: PropsWithChildren<
292
TRenderProps & {
293
form: AppFieldExtendedReactFormApi<
294
TFormData,
295
TOnMount,
296
TOnChange,
297
TOnChangeAsync,
298
TOnBlur,
299
TOnBlurAsync,
300
TOnSubmit,
301
TOnSubmitAsync,
302
TOnDynamic,
303
TOnDynamicAsync,
304
TOnServer,
305
TSubmitMeta,
306
TFieldComponents,
307
TFormComponents
308
>;
309
}
310
>,
311
) => JSX.Element;
312
}
313
```
314
315
### WithFieldGroupProps
316
317
Props interface for withFieldGroup higher-order component.
318
319
```typescript { .api }
320
interface WithFieldGroupProps<
321
TFieldGroupData,
322
TFieldComponents extends Record<string, ComponentType<any>>,
323
TFormComponents extends Record<string, ComponentType<any>>,
324
TSubmitMeta,
325
TRenderProps extends Record<string, unknown> = Record<string, never>,
326
> extends BaseFormOptions<TFieldGroupData, TSubmitMeta> {
327
/** Optional additional props for render function */
328
props?: TRenderProps;
329
330
/**
331
* Render function that receives field group instance and props
332
* @param props - Combined props with field group API
333
* @returns JSX element
334
*/
335
render: (
336
props: PropsWithChildren<
337
TRenderProps & {
338
group: AppFieldExtendedReactFieldGroupApi<
339
unknown,
340
TFieldGroupData,
341
string | FieldsMap<unknown, TFieldGroupData>,
342
undefined | FormValidateOrFn<unknown>,
343
undefined | FormValidateOrFn<unknown>,
344
undefined | FormAsyncValidateOrFn<unknown>,
345
undefined | FormValidateOrFn<unknown>,
346
undefined | FormAsyncValidateOrFn<unknown>,
347
undefined | FormValidateOrFn<unknown>,
348
undefined | FormAsyncValidateOrFn<unknown>,
349
undefined | FormValidateOrFn<unknown>,
350
undefined | FormAsyncValidateOrFn<unknown>,
351
undefined | FormAsyncValidateOrFn<unknown>,
352
unknown extends TSubmitMeta ? never : TSubmitMeta,
353
TFieldComponents,
354
TFormComponents
355
>;
356
}
357
>,
358
) => JSX.Element;
359
}
360
```
361
362
### AppFieldExtendedReactFormApi
363
364
Extended form API with custom components.
365
366
```typescript { .api }
367
type AppFieldExtendedReactFormApi<
368
TFormData,
369
TOnMount extends undefined | FormValidateOrFn<TFormData>,
370
TOnChange extends undefined | FormValidateOrFn<TFormData>,
371
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
372
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
373
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
374
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
375
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
376
TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
377
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
378
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
379
TSubmitMeta,
380
TFieldComponents extends Record<string, ComponentType<any>>,
381
TFormComponents extends Record<string, ComponentType<any>>,
382
> = ReactFormExtendedApi<
383
TFormData,
384
TOnMount,
385
TOnChange,
386
TOnChangeAsync,
387
TOnBlur,
388
TOnBlurAsync,
389
TOnSubmit,
390
TOnSubmitAsync,
391
TOnDynamic,
392
TOnDynamicAsync,
393
TOnServer,
394
TSubmitMeta
395
> &
396
TFormComponents & {
397
/** Field component with custom field components injected */
398
AppField: FieldComponent<
399
TFormData,
400
TOnMount,
401
TOnChange,
402
TOnChangeAsync,
403
TOnBlur,
404
TOnBlurAsync,
405
TOnSubmit,
406
TOnSubmitAsync,
407
TOnDynamic,
408
TOnDynamicAsync,
409
TOnServer,
410
TSubmitMeta,
411
TFieldComponents
412
>;
413
414
/** Form wrapper component with context provider */
415
AppForm: ComponentType<PropsWithChildren>;
416
};
417
```
418
419
## Utility Functions
420
421
### Form State Manipulation
422
423
```typescript { .api }
424
/**
425
* Applies an updater function or value to an input
426
* @param updater - Function or value to apply
427
* @param input - Input value
428
* @returns Updated value
429
*/
430
function functionalUpdate<TInput, TOutput = TInput>(
431
updater: Updater<TInput, TOutput>,
432
input: TInput,
433
): TOutput;
434
435
/**
436
* Merges partial state into a form instance
437
* @param baseForm - Base form API instance
438
* @param state - Partial state to merge
439
* @returns Form API with merged state
440
*/
441
function mergeForm<TFormData>(
442
baseForm: FormApi<TFormData, ...>,
443
state: Partial<FormApi<TFormData, ...>['state']>,
444
): FormApi<TFormData, ...>;
445
446
/**
447
* Deep merges source object into target object (mutating)
448
* @param target - Target object to merge into
449
* @param source - Source object to merge from
450
* @returns Merged object
451
*/
452
function mutateMergeDeep(
453
target: object | null | undefined,
454
source: object | null | undefined,
455
): object;
456
```
457
458
### Path Utilities
459
460
```typescript { .api }
461
/**
462
* Gets a value from an object using a path
463
* Supports dot notation and array indices
464
* @param obj - Object to traverse
465
* @param path - Path string or array
466
* @returns Value at path
467
*/
468
function getBy(obj: any, path: any): any;
469
470
/**
471
* Sets a value on an object using a path
472
* Supports dot notation and array indices
473
* @param obj - Object to update
474
* @param path - Path string or array
475
* @param updater - Value or function to set
476
* @returns Updated object
477
*/
478
function setBy(obj: any, path: any, updater: Updater<any>): any;
479
480
/**
481
* Deletes a field on an object using a path
482
* @param obj - Object to update
483
* @param path - Path to delete
484
* @returns Updated object
485
*/
486
function deleteBy(obj: any, path: any): any;
487
488
/**
489
* Converts a path string to an array of path segments
490
* @param str - Path string or array
491
* @returns Array of path segments
492
*/
493
function makePathArray(str: string | Array<string | number>): Array<string | number>;
494
495
/**
496
* Concatenates two paths together
497
* @param path1 - First path
498
* @param path2 - Second path
499
* @returns Concatenated path string
500
*/
501
function concatenatePaths(path1: string, path2: string): string;
502
```
503
504
### Helper Functions
505
506
```typescript { .api }
507
/**
508
* Creates type-safe form options with proper inference
509
* @param defaultOpts - Form options with custom properties
510
* @returns Type-safe form options
511
*/
512
function formOptions<TOptions, TFormData, ...>(
513
defaultOpts: Partial<FormOptions<TFormData, ...>> & TOptions,
514
): TOptions;
515
516
/**
517
* Creates a map of field keys to their names
518
* @param values - Object with field values
519
* @returns Map of keys to names
520
*/
521
function createFieldMap<T>(values: Readonly<T>): { [K in keyof T]: K };
522
523
/**
524
* Checks if a value is a non-empty array
525
* @param obj - Value to check
526
* @returns True if value is non-empty array
527
*/
528
function isNonEmptyArray(obj: any): boolean;
529
530
/**
531
* Generates a unique identifier
532
* @returns Unique ID string
533
*/
534
function uuid(): string;
535
536
/**
537
* Merges default options with provided options
538
* @param defaultOpts - Default options
539
* @param opts - Override options
540
* @returns Merged options
541
*/
542
function mergeOpts<T>(defaultOpts: T, opts?: T): T;
543
544
/**
545
* Deep equality check for two objects
546
* @param objA - First object
547
* @param objB - Second object
548
* @returns True if objects are deeply equal
549
*/
550
function evaluate<T>(objA: T, objB: T): boolean;
551
```
552
553
### Field Metadata Helpers
554
555
```typescript { .api }
556
/**
557
* Default field metadata object
558
*/
559
const defaultFieldMeta: AnyFieldMeta;
560
561
/**
562
* Helper function for managing field metadata during array operations
563
* @param formApi - The form API instance
564
* @returns Object with handleArrayFieldMetaShift method for managing field metadata
565
*/
566
function metaHelper<TFormData, ...>(
567
formApi: FormApi<TFormData, ...>
568
): {
569
handleArrayFieldMetaShift: (
570
field: DeepKeys<TFormData>,
571
index: number,
572
mode: 'insert' | 'remove' | 'swap' | 'move',
573
secondIndex?: number
574
) => void;
575
};
576
```
577
578
## Usage Examples
579
580
### Creating a Custom Form Hook with Component Library
581
582
```typescript
583
import { createFormHook, createFormHookContexts } from '@tanstack/react-form';
584
585
// Create contexts
586
const contexts = createFormHookContexts();
587
588
// Define custom components
589
const fieldComponents = {
590
// Custom input component with built-in styling
591
Input: ({ ...props }) => (
592
<input
593
className="custom-input"
594
{...props}
595
/>
596
),
597
598
// Custom select component
599
Select: ({ options, ...props }) => (
600
<select className="custom-select" {...props}>
601
{options.map((opt) => (
602
<option key={opt.value} value={opt.value}>
603
{opt.label}
604
</option>
605
))}
606
</select>
607
),
608
};
609
610
const formComponents = {
611
// Custom form wrapper
612
Card: ({ children }) => (
613
<div className="card">
614
<div className="card-body">{children}</div>
615
</div>
616
),
617
};
618
619
// Create custom hook
620
const { useAppForm, withForm, withFieldGroup } = createFormHook({
621
fieldComponents,
622
fieldContext: contexts.fieldContext,
623
formComponents,
624
formContext: contexts.formContext,
625
});
626
627
// Use the custom form hook
628
function MyForm() {
629
const form = useAppForm({
630
defaultValues: {
631
name: '',
632
color: 'red',
633
},
634
});
635
636
return (
637
<form.AppForm>
638
<form.Card>
639
<form.AppField name="name">
640
{(field) => (
641
<div>
642
<label>Name:</label>
643
<field.Input
644
value={field.state.value}
645
onChange={(e) => field.handleChange(e.target.value)}
646
/>
647
</div>
648
)}
649
</form.AppField>
650
651
<form.AppField name="color">
652
{(field) => (
653
<div>
654
<label>Color:</label>
655
<field.Select
656
options={[
657
{ value: 'red', label: 'Red' },
658
{ value: 'blue', label: 'Blue' },
659
]}
660
value={field.state.value}
661
onChange={(e) => field.handleChange(e.target.value)}
662
/>
663
</div>
664
)}
665
</form.AppField>
666
</form.Card>
667
</form.AppForm>
668
);
669
}
670
```
671
672
### Using withForm Higher-Order Component
673
674
```typescript
675
const { withForm } = createFormHook({
676
fieldComponents: {},
677
fieldContext: contexts.fieldContext,
678
formComponents: {},
679
formContext: contexts.formContext,
680
});
681
682
const ContactForm = withForm({
683
defaultValues: {
684
name: '',
685
email: '',
686
},
687
validators: {
688
onChange: ({ value }) => {
689
if (!value.email.includes('@')) {
690
return 'Invalid email';
691
}
692
return undefined;
693
},
694
},
695
onSubmit: async ({ value }) => {
696
await submitToServer(value);
697
},
698
render: ({ form }) => (
699
<form.AppForm>
700
<form.AppField name="name">
701
{(field) => (
702
<input
703
value={field.state.value}
704
onChange={(e) => field.handleChange(e.target.value)}
705
/>
706
)}
707
</form.AppField>
708
709
<form.AppField name="email">
710
{(field) => (
711
<input
712
value={field.state.value}
713
onChange={(e) => field.handleChange(e.target.value)}
714
/>
715
)}
716
</form.AppField>
717
718
<button type="submit">Submit</button>
719
</form.AppForm>
720
),
721
});
722
723
// Use the component
724
function App() {
725
return <ContactForm />;
726
}
727
```
728
729
### Using withFieldGroup for Reusable Address Input
730
731
```typescript
732
const { withFieldGroup } = createFormHook({
733
fieldComponents: {},
734
fieldContext: contexts.fieldContext,
735
formComponents: {},
736
formContext: contexts.formContext,
737
});
738
739
const AddressInput = withFieldGroup({
740
defaultValues: {
741
street: '',
742
city: '',
743
state: '',
744
zip: '',
745
},
746
render: ({ group }) => (
747
<div className="address-group">
748
<group.Field name="street">
749
{(field) => (
750
<input
751
placeholder="Street"
752
value={field.state.value}
753
onChange={(e) => field.handleChange(e.target.value)}
754
/>
755
)}
756
</group.Field>
757
758
<group.Field name="city">
759
{(field) => (
760
<input
761
placeholder="City"
762
value={field.state.value}
763
onChange={(e) => field.handleChange(e.target.value)}
764
/>
765
)}
766
</group.Field>
767
768
<group.Field name="state">
769
{(field) => (
770
<input
771
placeholder="State"
772
value={field.state.value}
773
onChange={(e) => field.handleChange(e.target.value)}
774
/>
775
)}
776
</group.Field>
777
778
<group.Field name="zip">
779
{(field) => (
780
<input
781
placeholder="ZIP"
782
value={field.state.value}
783
onChange={(e) => field.handleChange(e.target.value)}
784
/>
785
)}
786
</group.Field>
787
</div>
788
),
789
});
790
791
// Use in a form
792
function UserForm() {
793
const form = useAppForm({
794
defaultValues: {
795
name: '',
796
shippingAddress: {
797
street: '',
798
city: '',
799
state: '',
800
zip: '',
801
},
802
billingAddress: {
803
street: '',
804
city: '',
805
state: '',
806
zip: '',
807
},
808
},
809
});
810
811
return (
812
<form.AppForm>
813
<h2>Shipping Address</h2>
814
<AddressInput form={form} fields="shippingAddress" />
815
816
<h2>Billing Address</h2>
817
<AddressInput form={form} fields="billingAddress" />
818
</form.AppForm>
819
);
820
}
821
```
822
823
### Using Context Hooks
824
825
```typescript
826
import { createFormHookContexts } from '@tanstack/react-form';
827
828
const { useFieldContext, useFormContext } = createFormHookContexts();
829
830
// Custom field wrapper component
831
function FieldWrapper({ children }) {
832
const field = useFieldContext<string>();
833
834
return (
835
<div className="field-wrapper">
836
<div className="field-content">{children}</div>
837
{field.state.meta.errors[0] && (
838
<div className="field-error">{field.state.meta.errors[0]}</div>
839
)}
840
</div>
841
);
842
}
843
844
// Custom submit button component
845
function SubmitButton() {
846
const form = useFormContext();
847
848
return (
849
<button
850
type="submit"
851
disabled={!form.state.canSubmit || form.state.isSubmitting}
852
>
853
{form.state.isSubmitting ? 'Submitting...' : 'Submit'}
854
</button>
855
);
856
}
857
```
858
859
### Path Utilities for Dynamic Field Names
860
861
```typescript
862
import { getBy, setBy, concatenatePaths } from '@tanstack/react-form';
863
864
function DynamicFieldName() {
865
const form = useForm({
866
defaultValues: {
867
user: {
868
profile: {
869
firstName: 'John',
870
},
871
},
872
},
873
});
874
875
const basePath = 'user.profile';
876
const fieldName = 'firstName';
877
const fullPath = concatenatePaths(basePath, fieldName);
878
879
// Get value using path
880
const value = getBy(form.state.values, fullPath);
881
console.log(value); // 'John'
882
883
// Set value using path
884
const updated = setBy(
885
form.state.values,
886
fullPath,
887
(current) => current.toUpperCase()
888
);
889
890
return <div>{value}</div>;
891
}
892
```
893
894
## DevTools Integration
895
896
### Event Client
897
898
Event client for integrating with TanStack Form DevTools for debugging and monitoring form state.
899
900
```typescript { .api }
901
/**
902
* Event client instance for form devtools integration
903
* Used internally by FormApi to broadcast form state changes
904
*/
905
const formEventClient: FormEventClient;
906
907
/**
908
* Form state change broadcast event payload
909
*/
910
type BroadcastFormState = {
911
/** Unique form identifier */
912
id: string;
913
/** Current form state */
914
state: AnyFormState;
915
/** Form options */
916
options: AnyFormOptions;
917
};
918
919
/**
920
* Form submission state change broadcast event payload
921
*/
922
type BroadcastFormSubmissionState =
923
| {
924
id: string;
925
submissionAttempt: number;
926
successful: false;
927
stage: 'validateAllFields' | 'validate';
928
errors: any[];
929
}
930
| {
931
id: string;
932
submissionAttempt: number;
933
successful: false;
934
stage: 'inflight';
935
onError: unknown;
936
}
937
| {
938
id: string;
939
submissionAttempt: number;
940
successful: true;
941
};
942
943
/**
944
* Form unmounted event payload
945
*/
946
type BroadcastFormUnmounted = {
947
/** Form identifier that was unmounted */
948
id: string;
949
};
950
951
/**
952
* Request form state event payload
953
*/
954
type RequestFormState = {
955
/** Form identifier to request state for */
956
id: string;
957
};
958
959
/**
960
* Request form reset event payload
961
*/
962
type RequestFormReset = {
963
/** Form identifier to reset */
964
id: string;
965
};
966
967
/**
968
* Request form force reset event payload
969
*/
970
type RequestFormForceReset = {
971
/** Form identifier to force reset */
972
id: string;
973
};
974
975
/**
976
* Event client event map type
977
* Maps event names to their payload types
978
*/
979
type EventClientEventMap = keyof {
980
'form-devtools:form-state-change': BroadcastFormState;
981
'form-devtools:form-submission-state-change': BroadcastFormSubmissionState;
982
'form-devtools:form-unmounted': BroadcastFormUnmounted;
983
'form-devtools:request-form-state': RequestFormState;
984
'form-devtools:request-form-reset': RequestFormReset;
985
'form-devtools:request-form-force-submit': RequestFormForceReset;
986
};
987
988
/**
989
* Extracted event names from event client event map
990
*/
991
type EventClientEventNames =
992
| 'form-state-change'
993
| 'form-submission-state-change'
994
| 'form-unmounted'
995
| 'request-form-state'
996
| 'request-form-reset'
997
| 'request-form-force-submit';
998
```
999
1000
**Note:** The Event Client is primarily for internal use by TanStack Form DevTools. Most applications do not need to interact with it directly.
1001