Skip to main content

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

NameTypeDefaultDescription
formUseFormReturn<T>-External form instance from useForm(). If not provided, one will be created internally
actionstring-API endpoint URL for form submission
method'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE''POST'HTTP method for the request
formOptionsUseFormProps<T>-Options passed to React Hook Form's useForm()
onSubmitSubmitHandler<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
successMessagestring'Saved successfully!'Success toast message
errorMessagestring'Something went wrong! Please try again.'Error toast message
submitBtnbooleantrueWhether to show the submit button
submitBtnTextstring'Save'Text for the submit button
loadingbooleanfalseExternal loading state that disables the form
childrenReact.ReactNode-Form fields and other content
classNamestring-CSS class for the form element
noValidatebooleantrueWhether 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:

  1. Sends a request to the action URL using the specified method
  2. Includes form data as JSON in the request body
  3. Handles the response:
    • If result.error exists, calls onError or shows error toast
    • Otherwise, calls onSuccess or shows success toast

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 when loading is true
  • The component uses React Hook Form's shouldUnregister: true by default
  • Success and error messages are displayed using react-toastify
  • All form fields must be inside the <Form> component to be included in submission