Form
Description
A flexible form component built on top of React Hook Form that handles form submission, validation, error handling, and success notifications. It provides automatic API integration with customizable callbacks for success and error scenarios.
Import
import { Form } from '@components/common/form/Form';
Usage
import { Form } from '@components/common/form/Form';
import { InputField } from '@components/common/form/InputField';
function MyForm() {
return (
<Form
action="/api/save"
method="POST"
onSuccess={(response) => {
console.log('Form saved:', response);
}}
>
<InputField name="name" label="Name" placeholder="Enter name" />
<InputField name="email" type="email" label="Email" placeholder="Enter email" />
</Form>
);
}
Props
| Name | Type | Default | Description |
|---|---|---|---|
| form | UseFormReturn<T> | - | External form instance from useForm(). If not provided, one will be created internally |
| action | string | - | API endpoint URL for form submission |
| method | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'POST' | HTTP method for the request |
| formOptions | UseFormProps<T> | - | Options passed to React Hook Form's useForm() |
| onSubmit | SubmitHandler<T> | - | Custom submit handler. If not provided, uses default API submission |
| onSuccess | (response: any, data: T) => void | - | Callback fired when submission succeeds |
| onError | (error: string, data: T) => void | - | Callback fired when submission fails |
| successMessage | string | 'Saved successfully!' | Success toast message |
| errorMessage | string | 'Something went wrong! Please try again.' | Error toast message |
| submitBtn | boolean | true | Whether to show the submit button |
| submitBtnText | string | 'Save' | Text for the submit button |
| loading | boolean | false | External loading state that disables the form |
| children | React.ReactNode | - | Form fields and other content |
| className | string | - | CSS class for the form element |
| noValidate | boolean | true | Whether to disable browser validation |
Basic Example
import { Form } from '@components/common/form/Form';
import { InputField } from '@components/common/form/InputField';
import { TextAreaField } from '@components/common/form/TextAreaField';
function ContactForm() {
return (
<Form
action="/api/contact"
method="POST"
successMessage="Thank you for contacting us!"
errorMessage="Failed to send message. Please try again."
>
<InputField name="name" label="Name" required />
<InputField name="email" type="email" label="Email" required />
<TextAreaField name="message" label="Message" required />
</Form>
);
}
Example: Custom Submit Handler
import { Form } from '@components/common/form/Form';
import { InputField } from '@components/common/form/InputField';
import { toast } from 'react-toastify';
function CustomForm() {
const handleSubmit = async (data) => {
// Custom logic before submission
console.log('Form data:', data);
// Make your own API call
const response = await fetch('/api/custom', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
toast.success('Success!');
}
};
return (
<Form onSubmit={handleSubmit} submitBtn={false}>
<InputField name="field1" label="Field 1" />
<InputField name="field2" label="Field 2" />
<button type="submit">Custom Submit Button</button>
</Form>
);
}
Example: With Success/Error Callbacks
import { Form } from '@components/common/form/Form';
import { useRouter } from 'next/router';
function ProductForm() {
const router = useRouter();
const handleSuccess = (response, data) => {
console.log('Product created:', response.data);
// Redirect to product page
router.push(`/products/${response.data.id}`);
};
const handleError = (error, data) => {
console.error('Failed to create product:', error);
// Custom error handling
if (error.includes('duplicate')) {
toast.error('Product already exists');
}
};
return (
<Form
action="/api/products"
method="POST"
onSuccess={handleSuccess}
onError={handleError}
submitBtnText="Create Product"
>
<input name="name" placeholder="Product name" />
<input name="price" type="number" placeholder="Price" />
</Form>
);
}
Example: Using External Form Instance
import { Form } from '@components/common/form/Form';
import { useForm } from 'react-hook-form';
function AdvancedForm() {
const form = useForm({
defaultValues: {
name: '',
email: '',
age: 0
},
mode: 'onChange'
});
const { watch } = form;
const name = watch('name');
return (
<div>
<p>Current name: {name}</p>
<Form
form={form}
action="/api/user"
formOptions={{
mode: 'onChange'
}}
>
<input {...form.register('name')} />
<input {...form.register('email')} />
<input {...form.register('age', { valueAsNumber: true })} />
</Form>
</div>
);
}
Example: With Validation
import { Form, useFormContext } from '@components/common/form/Form';
function ValidatedField() {
const { register, formState: { errors } } = useFormContext();
return (
<div>
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address'
}
})}
/>
{errors.email && (
<span className="error">{errors.email.message}</span>
)}
</div>
);
}
function ValidatedForm() {
return (
<Form action="/api/subscribe">
<ValidatedField />
</Form>
);
}
Example: Disabled Form with Loading State
import { Form } from '@components/common/form/Form';
import { useState } from 'react';
function FormWithLoading() {
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (data) => {
setIsLoading(true);
try {
await someAsyncOperation(data);
} finally {
setIsLoading(false);
}
};
return (
<Form
onSubmit={handleSubmit}
loading={isLoading}
submitBtnText="Processing..."
>
<input name="field1" />
<input name="field2" />
</Form>
);
}
Default Behavior
Automatic API Submission
If no onSubmit handler is provided, the form automatically:
- Sends a request to the
actionURL using the specifiedmethod - Includes form data as JSON in the request body
- Handles the response:
- If
result.errorexists, callsonErroror shows error toast - Otherwise, calls
onSuccessor shows success toast
- If
Response Format
The default handler expects this response format:
// Success response
{
data: { /* your data */ }
}
// Error response
{
error: {
message: "Error description"
}
}
Features
- Built on React Hook Form: Full access to React Hook Form features
- Automatic API Integration: Default submission handler with fetch API
- Toast Notifications: Automatic success/error notifications
- Loading States: Disables form during submission
- Flexible Submit Button: Show/hide or customize the submit button
- External Form Control: Use your own form instance for advanced control
- TypeScript Support: Full type safety with generics
Exported Utilities
The Form component also exports useful utilities from React Hook Form:
import {
Form,
useFormContext, // Access form context in child components
Controller, // Controlled input wrapper
Control, // Type for form control
FieldPath, // Type for field paths
FieldValues // Type for field values
} from '@components/common/form/Form';
Notes
- The form automatically prevents default browser validation with
noValidate={true} - Form fields are wrapped in a
<fieldset>that gets disabled during submission or whenloadingis true - The component uses React Hook Form's
shouldUnregister: trueby default - Success and error messages are displayed using
react-toastify - All form fields must be inside the
<Form>component to be included in submission