Create schema form creates a typesafe reusable form component based on your zod-to-component mapping.
const Form = createTsForm(mapping, options);
Typically you'll do this once per project, as the mapping can map any number of schemas to any number of components.
mapping - A zod-to-component mapping. An array of two-tuples where the first element is a zod schema and the second element is a React functional component:
const mapping = [
[z.string(), TextField],
[z.boolean(), CheckboxField],
] as const;
The zod schema on the left determines which properties in your form schemas get mapped to which components:
const FormSchema = z.object({
name: z.string(), // Maps to TextField
isOver18: z.boolean(), // Maps to CheckBoxField
});
You can use any zod schema. Objects get matched based on their properties.
options - (optional) Allows further customization of the form:
const Form = createTsForm(mapping, {
FormComponent: CustomFormComponent,
propsMap: [["name", "someOtherPropName"]] as const,
});
-
options.FormComponent - (optional)A custom form component to use as the container for your field components. Defaults to an html "form". It will be passed a "onSubmit" and "children" prop and should render the children:
function FormContainer({ children, onSubmit, }: { children: ReactNode; onSubmit: () => void; }) { return ( <form onSubmit={onSubmit}> {children} <button>Submit</button> </form> ); } const MyForm = createTsForm(mapping, { FormComponent: FormContainer, });
- options.propsMap - (optional) Controls which props get passed to your component as well as allows customizing their name. An array of tuples where the first element is a the name of a mappable prop, and the second element is the name of the prop you want it forwarded too. Any elements not included in the array will not be passed to input components, and you will not be able to pass any props included on the right hand side to your components via the
props
prop from theForm
component.
function MyComponent({ myCustomPropName, myCustomControlName, }: { myCustomPropName: string; // receives name myCustomControlName: string; // receives control }) { //... } const propsMap = [ ["name", "myCustomPropName"], ["control", "myCustomControlName"], ]; const componentMap = [[z.string(), MyComponent]] as const; const Form = createTsForm(componentMap, { propsMap, });
Defaults to:
[ ["name", "name"], ["control", "control"], ["enumValues", "enumValues"], ];
Mappable props are:
name
- the name of the input (the property name in your zod schema).control
- the react hook form controlenumValues
- (deprecated)enumValues
extracted from your zod enum schema.label
- The label extracted from.describe()
placeholder
- The placeholder extracted from.describe()
- options.propsMap - (optional) Controls which props get passed to your component as well as allows customizing their name. An array of tuples where the first element is a the name of a mappable prop, and the second element is the name of the prop you want it forwarded too. Any elements not included in the array will not be passed to input components, and you will not be able to pass any props included on the right hand side to your components via the
This can be useful in cases where you would like to integrate with existing components, or just don't want @ts-react/form
to forward any props for you.
This is useful when dealing with multiple schemas of the same type that you would like to have mapped to different components:
const BigTextFieldSchema = createUniqueFieldSchema(z.string(), "id"); // need to pass a unique string literal
const mapping = [
[z.string(), TextField],
[BigTextFieldSchema, BigTextField],
] as const;
const FormSchema = z.object({
name: z.string(), // renders as TextField
bigName: BigTextFieldSchema, // renders as BigTextField
});
This is the component returned via createSchemaForm
Prop | Req | Type | Description |
---|---|---|---|
schema | Yes | AnyZodObject | A zod object that will be used to validate your form input. |
onSubmit | Yes | (schema: DataType)=>void | A function that will be called when the form is submitted and validated successfully. |
props | Maybe | Record<string, ComponentProps> | props to pass to your components. Will be required if any of your components have required props, optional otherwise. |
formProps | Maybe | FormProps | props to pass to your form, typesafe to your form component props. |
defaultValues | No | DeepPartial | Default values for your form. |
renderAfter | No | ({submit}:{submit: ()=>void})=>ReactNode | A function that returns an element to be rendered after your form inputs, inside the form container. Is passed the submit function that will try to submit the form. |
renderBefore | No | ({submit}:{submit: ()=>void})=>ReactNode | A function that returns an element to be rendered before your form inputs, inside the form container. Is passed the submit function that will try to submit the form. |
form | No | UseFormReturn | Optionally pass a react-hook-form useForm() result so that you can have control of your form state in the parent component. |
A typesafe hook that automatically connects to your form state:
function TextField() {
const {
field: { onChange, value },
error,
} = useTsController<string>();
// ...
}
Returns everything that react-hook-form
's useController returns plus an extra error
object and slightly modified typings to make the form state more intuitive to work with.
Returns the label
and placeholder
extracted from a call to .describe()
:
function TextField() {
const { label, placeholder } = useDescription(); // {label?: string, placeholder?: string};
// ...
}
const FormSchema = z.object({
name: z.string().describe("Name // Please enter your name"),
});
Note you can also pass labels and placeholders and normal react props if you prefer.
Exactly the same as useDescription
, except it will throw an error if either label
or placeholder
is not passed via the .describe()
syntax. Useful if you want to make sure they're always passed.
Returns enum values passed to z.enum()
for the field that's being rendered:
function MyDropdown() {
const values = useEnumValues(); // ['red', 'green', 'blue']
return (
<select>
{values.map((e) => {
//...
})}
</select>
);
}
const FormSchema = z.object({
favoriteColor: z.enum(["red", "green", "blue"]),
});