This pattern allows you to manage a single form state in a parent layout component and access all form methods in child components rendered through React Router's Outlet.
AddProductLayout (Parent)
├── useForm() - Form state management
├── <form> - Form wrapper
├── <Outlet /> - Child components
└── Submit button
The provider initializes useForm and makes it available to all child components:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { addProductSchema } from "@/schemas/add-product";
export default function ProductCreationProvider({ children }) {
const formMethods = useForm({
resolver: zodResolver(addProductSchema),
defaultValues: ADD_PRODUCT_INITIAL_STATE,
});
return (
<ProductCreationContext.Provider value={{
form,
setForm,
resetForm,
formMethods
}}>
{children}
</ProductCreationContext.Provider>
);
}Define the context type to include form methods:
type ProductCreationContextType = {
form: Product;
setForm: React.Dispatch<React.SetStateAction<Product>>;
resetForm: () => void;
formMethods: UseFormReturn<z.infer<typeof addProductSchema>>;
};The layout component handles form submission and renders the form wrapper:
function AddProductForm() {
const { formMethods } = useContext(ProductCreationContext);
const { handleSubmit } = formMethods;
const onSubmit = (data) => {
console.log("Form submitted:", data);
// Handle form submission
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h2>Add New Product</h2>
<Outlet /> {/* Child components go here */}
<Button type="submit">Create Product</Button>
</form>
);
}Child components can access all form methods through context:
// Using the main hook
import useProductCreation from "@/hooks/use-product-creation";
export default function AddProductInfo() {
const { formMethods } = useProductCreation();
const { register, formState: { errors }, setValue } = formMethods;
return (
<div>
<Input {...register("name")} />
<ErrorMessage message={errors.name?.message} />
</div>
);
}// Using the dedicated form hook
import useProductForm from "@/hooks/use-product-form";
export default function ExampleChildComponent() {
const { register, formState: { errors }, watch } = useProductForm();
return (
<div>
<Input {...register("price")} />
<div>Current values: {JSON.stringify(watch())}</div>
</div>
);
}Returns the complete context including form state and methods:
const { form, setForm, resetForm, formMethods } = useProductCreation();Returns only the form methods for cleaner access:
const { register, handleSubmit, formState, setValue, watch } = useProductForm();- Single Form State: All form data is managed in one place
- Shared Validation: Form validation works across all child components
- Centralized Submission: Form submission is handled in the parent
- Type Safety: Full TypeScript support with proper typing
- Reusable: Child components can be easily reused in different forms
const { register, formState: { errors } } = useProductForm();
<Input {...register("name")} />
<ErrorMessage message={errors.name?.message} />const { control } = useProductForm();
const { fields, append, remove } = useFieldArray({
name: "tags",
control
});
{fields.map((field, index) => (
<Input {...register(`tags.${index}.text`)} />
))}const { setValue } = useProductForm();
const handleFileUpload = (file) => {
const url = URL.createObjectURL(file);
setValue("images.0.url", url);
};const { watch } = useProductForm();
const formValues = watch();
// Watch specific fields
const name = watch("name");- Always wrap child components with the
ProductCreationProvider - Use the dedicated hooks for cleaner code
- Handle form submission only in the parent layout
- Validate form state before allowing navigation between steps
- Reset form when needed using the
resetFormfunction
The hooks include proper error handling:
const { formMethods } = useProductCreation();
if (!formMethods) {
throw new Error("useProductCreation must be used within ProductCreationProvider");
}This pattern provides a clean, type-safe way to manage complex forms across multiple components while maintaining a single source of truth for form state.