When using react-hook-form and useFormContext with multple forms inside the provider, on submit, how do I get each useFormContext data part as a separate object?
While useFormContext
in react-hook-form
allows accessing form context within a FormProvider
, it doesn’t directly solve submitting multiple forms with separate data objects. Here are two common approaches to achieve this:
1. Wrapping each form with its own FormProvider
:
This approach uses a dedicated FormProvider
and useForm
for each individual form. Upon submission, each form’s handleSubmit
function returns the form data as a separate object.
Here’s an example:
import { Form, FormProvider, useForm, useFormContext } from "react-hook-form";
const Form1 = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log("Form 1 data:", data); // This is a separate object
};
return (
<FormProvider {...register}>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" {...register("name", { required: true })} />
{errors.name && <span>Name is required</span>}
<button type="submit">Submit Form 1</button>
</form>
</FormProvider>
);
};
const Form2 = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log("Form 2 data:", data); // This is another separate object
};
return (
<FormProvider {...register}>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="email" {...register("email", { required: true })} />
{errors.email && <span>Email is required</span>}
<button type="submit">Submit Form 2</button>
</form>
</FormProvider>
);
};
const App = () => {
return (
<div>
<Form1 />
<Form2 />
</div>
);
};
export default App;
In this example, Form1
and Form2
each handle their own data through useForm
and submit separate objects on submission.
2. Using a custom hook to manage multiple forms:
This approach creates a custom hook to manage multiple forms with a single FormProvider
. The hook uses an internal state to store form data for each form and exposes functions to access and submit data.
Here’s a simplified example:
import { Form, useForm } from "react-hook-form";
const useMultiForm = () => {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const [formData, setFormData] = React.useState({ form1: {}, form2: {} });
const handleForm1Submit = (data) => {
setFormData({ ...formData, form1: data });
// You can also perform further actions with data here
};
const handleForm2Submit = (data) => {
setFormData({ ...formData, form2: data });
// You can also perform further actions with data here
};
return {
register,
handleSubmit,
watch,
errors,
formData,
handleForm1Submit,
handleForm2Submit,
};
};
const App = () => {
const { register, handleSubmit, watch, errors, formData, handleForm1Submit, handleForm2Submit } = useMultiForm();
return (
<div>
<Form onSubmit={handleSubmit(handleForm1Submit)}>
{/* Form 1 fields with register */}
</Form>
<Form onSubmit={handleSubmit(handleForm2Submit)}>
{/* Form 2 fields with register */}
</Form>
<pre>{JSON.stringify(formData, null, 2)}</pre>
</div>
);
};
export default App;
This approach maintains a single FormProvider
while managing data and submissions within the custom hook. You can customize it further to fit your specific needs, such as handling form validation and error handling.
Both approaches handle submitting multiple forms with separate data objects. Choose the one that best suits your application’s structure and complexity.
It seems this can be solved by prefixing the key used for inputs. Please show an example.
Is there a way to auto-prefix so I do not need to write it each time?
Unfortunately, react-hook-form
doesn’t offer built-in functionality for automatic prefixing. However, you can achieve it using a custom wrapper component or higher-order component (HOC). Here’s an example using a wrapper component:
import { Form, useForm } from "react-hook-form";
const FormWrapper = ({ children, formName }) => {
const { register, handleSubmit, formState: { errors } } = useForm();
const handleFormSubmit = (data) => {
console.log(`${formName} data:`, data);
};
return (
<Form onSubmit={handleSubmit(handleFormSubmit)}>
{children.map((child) => {
return React.cloneElement(child, {
...child.props,
{...register(`${formName}.${child.props.name}`, child.props)},
});
})}
{errors &&
Object.entries(errors).map(([key, value]) => (
<span key={key}>{value.message}</span>
))}
<button type="submit">Submit</button>
</Form>
);
};
const App = () => {
return (
<div>
<FormWrapper formName="form1">
<input type="text" name="name" placeholder="Name" />
<input type="email" name="email" placeholder="Email" />
</FormWrapper>
<FormWrapper formName="form2">
<input type="text" name="phone" placeholder="Phone Number" />
<input type="text" name="address" placeholder="Address" />
</FormWrapper>
</div>
);
};
export default App;
This approach:
- Defines a
FormWrapper
component that takeschildren
andformName
as props. - Uses
useForm
within the wrapper and defineshandleFormSubmit
to log data with the providedformName
. - Iterates through
children
and clones each element, adding theregister
prop with the prefixed name (formName.child.props.name
). - Renders any form errors based on the combined errors object.
This way, you can wrap your forms with the FormWrapper
, providing the desired form name, and it automatically prefixes the input names with the form name. Remember to adjust this example to match your specific component structure and error handling needs.