Factory Hooks
Create reusable typed hooks with createFormHook.
When multiple components need to work with the same form schema, createFormHook generates a set of hooks that share type information. This is useful for large forms split across files or when building a library of form components.
Why Factory Hooks?
With useForm, each component that calls the hook gets its own form instance. This works for single-file forms but breaks down when:
- Multiple components need to read/write the same form
- You want to enforce a specific schema across related components
- Sub-components need type-safe access to nested fields
createFormHook solves this by creating paired hooks: one to create the form, one to access it from anywhere in the tree.
Basic Usage
import React from "react";
import { } from "zod";
import { } from "antd-typed";
import { , , } from "antd";
const = .({
: .().(1),
: .().(),
: .({
: .(),
: .(),
}),
});
// Create typed hooks for this schema
export const {
: ,
: ,
} = ({
: ,
});This creates two hooks:
| Hook | Purpose |
|---|---|
useUserForm | Creates the form, returns Form, FormItem, etc. |
useUserFormInstance | Accesses the form from child components |
Using the Hooks
The parent component creates the form:
function () {
const { , } = ({
: () => {
// values is fully typed from userSchema
.();
},
});
return (
< ="vertical">
< ="name" ="Name">
< />
</>
< ="email" ="Email">
< />
</>
< />
< ="primary" ="submit">
Submit
</>
</>
);
}Child components access the form with scoped types:
function () {
// inheritAt scopes types to the address object
const { } = ({ : ["address"] });
return (
<>
{/* Only address fields are valid here */}
< ="street" ="Street">
< />
</>
< ="city" ="City">
< />
</>
</>
);
}The inheritAt option narrows the available field names to just those under address. Using an invalid name is a type error:
function () {
const { } = ({ : ["address"] });
// Only "street" | "city" are valid here
return (
<>
< ="street">
< />
</>
< name="name"> < />
</>
</>
);
}inheritAt vs inherit
There are two ways to scope child components:
inheritAt (Explicit)
Specify the exact path. Types are checked at compile time.
function () {
const { } = ({ : ["address"] });
// FormItem only accepts "street" | "city"
return (
< ="street">
< />
</>
);
}inherit (Dynamic)
Read the path from context. More flexible but less type-safe.
import React from "react";
import { useFormInstance } from "antd-typed";
import { Input } from "antd";
type Address = { street: string; city: string };
function AddressFields() {
// Type is provided manually
const { FormItem } = useFormInstance<Address>({ inherit: true });
return (
<FormItem name="street">
<Input />
</FormItem>
);
}Use inheritAt when the component is tied to a specific form. Use inherit when the component is generic and reused across forms.
Watching Values
Both hooks provide a typed useWatch for reactive field access:
function () {
const { } = ();
const name = ("name");
return <>Hello, { ?? "stranger"}</>;
}Project Organization
A common pattern is to define form hooks in a shared file:
src/
forms/
user-form.ts # createFormHook for user schema
company-form.ts # createFormHook for company schema
components/
UserForm.tsx # Uses useUserForm
AddressSection.tsx # Uses useUserFormInstance// forms/user-form.ts
import { createFormHook } from "antd-typed";
import { userSchema } from "../schemas";
export const {
useCustomForm: useUserForm,
useCustomFormInstance: useUserFormInstance,
} = createFormHook({ validator: userSchema });Components import the specific hooks they need, getting full type safety tied to that schema.
useForm vs createFormHook
| Feature | useForm | createFormHook |
|---|---|---|
| Single component forms | Yes | Overkill |
| Multi-component forms | Works with useFormInstance | Preferred |
Type-safe inheritAt | No | Yes |
| Reusable across files | Limited | Designed for it |
For simple forms contained in one component, useForm is sufficient. For forms split across multiple components or files, createFormHook provides better type safety and organization.