-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathform-renderer.tsx
117 lines (113 loc) · 3.22 KB
/
form-renderer.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import React from 'react';
import { UseFormProps, UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { FieldRenderer, RendererMap } from './renderer-map';
import { TRenderer, TSchema, useFormRenderer } from './use-form-renderer';
// We allow all props to be passed through, except for children and onSubmit.
type NativeFormProps = Omit<
React.ComponentPropsWithRef<'form'>,
'children' | 'onSubmit'
>;
/**
* This components translates a zod validation schema into a set of form controls
* by inferring the correct renderer for each field type.
* Please note, that this only works for primitive types and enums.
*
* It wraps the controls in a form tag, which can be customized with the formProps prop.
* To create a type renderer map, use the @see createRendererMap function.
*/
export const FormRenderer = <
TShape extends z.ZodRawShape,
TKey extends keyof TShape & string,
TCustomKey extends keyof TShape & string,
TFormValues extends z.infer<TSchema<TShape>>,
TStringProps,
TNumberProps,
TBooleanProps,
TEnumProps,
TDateProps,
TSubmitProps
>({
schema,
typeRendererMap,
fieldRendererMap = {},
useFormProps = {},
onSubmit,
children,
...rest
}: NativeFormProps & {
schema: TSchema<TShape>;
typeRendererMap: RendererMap<
TStringProps,
TNumberProps,
TBooleanProps,
TEnumProps,
TDateProps,
TSubmitProps
>;
fieldRendererMap?: Partial<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[K in TCustomKey]: FieldRenderer<any>;
}>;
useFormProps?: UseFormProps<TFormValues>;
onSubmit: (data: TFormValues, form: UseFormReturn<TFormValues>) => void;
children: (children: {
controls: {
Submit: RendererMap<
TStringProps,
TNumberProps,
TBooleanProps,
TEnumProps,
TDateProps,
TSubmitProps
>['Submit'];
} & {
[K in Capitalize<TKey>]: Uncapitalize<K> extends keyof Partial<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[K in TCustomKey]: FieldRenderer<any>;
}> &
object
? Partial<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[K in TCustomKey]: FieldRenderer<any>;
}>[Uncapitalize<K>] extends never
? never
: <TProps>(props: TProps) => React.ReactNode
: TRenderer<
TShape[Uncapitalize<K>],
RendererMap<
TStringProps,
TNumberProps,
TBooleanProps,
TEnumProps,
TDateProps,
TSubmitProps
>
>;
};
form: UseFormReturn<TFormValues>;
}) => React.ReactNode;
}) => {
// Force the consumer-facing controls object into the correct shape.
type TControls = Parameters<typeof children>[0]['controls'];
const { form, controls } = useFormRenderer<
TShape,
TCustomKey,
TFormValues,
TControls,
TStringProps,
TNumberProps,
TBooleanProps,
TEnumProps,
TDateProps,
TSubmitProps
>(schema, typeRendererMap, fieldRendererMap, useFormProps);
return (
<form
{...rest}
onSubmit={form.handleSubmit((data) => onSubmit(data, form))}
>
{children({ controls, form })}
</form>
);
};