Forms & Validation
Create forms with validation using Ant Design Form components.
Overview
EZ-Console uses Ant Design's Form component for form management and validation. This guide covers creating forms, validation rules, and handling form submissions.
Basic Form
Simple Form
import { Form, Input, Button } from 'antd';
import { useTranslation } from 'ez-console';
export const ProductForm: React.FC = () => {
const { t } = useTranslation();
const [form] = Form.useForm();
const handleSubmit = (values: any) => {
console.log('Form values:', values);
};
return (
<Form form={form} onFinish={handleSubmit} layout="vertical">
<Form.Item
name="name"
label={t('product.name')}
rules={[{ required: true, message: t('product.nameRequired') }]}
>
<Input placeholder={t('product.namePlaceholder')} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
{t('common.submit')}
</Button>
</Form.Item>
</Form>
);
};
Validation Rules
Built-in Rules
<Form.Item
name="email"
label="Email"
rules={[
{ required: true, message: 'Email is required' },
{ type: 'email', message: 'Invalid email format' },
]}
>
<Input />
</Form.Item>
<Form.Item
name="age"
label="Age"
rules={[
{ required: true, message: 'Age is required' },
{ type: 'number', min: 0, max: 120, message: 'Age must be between 0 and 120' },
]}
>
<InputNumber />
</Form.Item>
Custom Validation
<Form.Item
name="password"
label="Password"
rules={[
{ required: true, message: 'Password is required' },
{
validator: (_, value) => {
if (!value) {
return Promise.resolve();
}
if (value.length < 8) {
return Promise.reject(new Error('Password must be at least 8 characters'));
}
if (!/[A-Z]/.test(value)) {
return Promise.reject(new Error('Password must contain uppercase letter'));
}
if (!/[0-9]/.test(value)) {
return Promise.reject(new Error('Password must contain a number'));
}
return Promise.resolve();
},
},
]}
>
<Input.Password />
</Form.Item>
Async Validation
<Form.Item
name="username"
label="Username"
rules={[
{ required: true, message: 'Username is required' },
{
validator: async (_, value) => {
if (!value) {
return Promise.resolve();
}
const exists = await checkUsernameExists(value);
if (exists) {
return Promise.reject(new Error('Username already exists'));
}
return Promise.resolve();
},
},
]}
>
<Input />
</Form.Item>
Form Layouts
Vertical Layout (Default)
<Form layout="vertical">
<Form.Item name="name" label="Name">
<Input />
</Form.Item>
</Form>
Horizontal Layout
<Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
<Form.Item name="name" label="Name">
<Input />
</Form.Item>
</Form>
Inline Layout
<Form layout="inline">
<Form.Item name="search">
<Input placeholder="Search" />
</Form.Item>
<Form.Item>
<Button type="primary">Search</Button>
</Form.Item>
</Form>
Form Controls
Input
<Form.Item name="name" label="Name">
<Input placeholder="Enter name" />
</Form.Item>
<Form.Item name="description" label="Description">
<Input.TextArea rows={4} placeholder="Enter description" />
</Form.Item>
Select
<Form.Item name="category" label="Category">
<Select placeholder="Select category">
<Option value="electronics">Electronics</Option>
<Option value="clothing">Clothing</Option>
<Option value="food">Food</Option>
</Select>
</Form.Item>
DatePicker
import { DatePicker } from 'antd';
import dayjs from 'dayjs';
<Form.Item name="date" label="Date">
<DatePicker format="YYYY-MM-DD" />
</Form.Item>
Upload
import { Upload } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
<Form.Item name="file" label="File">
<Upload>
<Button icon={<UploadOutlined />}>Upload</Button>
</Upload>
</Form.Item>
Form Methods
Programmatic Control
const [form] = Form.useForm();
// Set field values
form.setFieldsValue({
name: 'Product Name',
price: 99.99,
});
// Get field values
const values = form.getFieldsValue();
// Validate form
form.validateFields().then(values => {
console.log('Valid values:', values);
});
// Reset form
form.resetFields();
// Reset specific fields
form.resetFields(['name', 'price']);
Conditional Fields
const [showAdvanced, setShowAdvanced] = useState(false);
<Form.Item name="type" label="Type">
<Select onChange={(value) => setShowAdvanced(value === 'advanced')}>
<Option value="basic">Basic</Option>
<Option value="advanced">Advanced</Option>
</Select>
</Form.Item>
{showAdvanced && (
<Form.Item name="advancedOption" label="Advanced Option">
<Input />
</Form.Item>
)}
Form Submission
Basic Submission
const handleSubmit = (values: any) => {
console.log('Form values:', values);
// Submit to API
};
With API Call
import { useRequest } from 'ahooks';
import { apiPost } from 'ez-console';
import { message } from 'antd';
export const ProductForm: React.FC = () => {
const [form] = Form.useForm();
const navigate = useNavigate();
const { run: submitForm, loading } = useRequest(
(values: Product) => apiPost('/products', values),
{
manual: true,
onSuccess: () => {
message.success('Product created successfully');
navigate('/products');
},
onError: (error) => {
message.error(error.message || 'Failed to create product');
},
}
);
const handleSubmit = (values: any) => {
submitForm(values);
};
return (
<Form form={form} onFinish={handleSubmit}>
{/* Form fields */}
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading}>
Submit
</Button>
</Form.Item>
</Form>
);
};
Edit Form
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useRequest } from 'ahooks';
import { apiGet, apiPut } from 'ez-console';
export const ProductEditForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [form] = Form.useForm();
// Fetch product data
const { data: product, loading: loadingProduct } = useRequest(
() => apiGet(`/products/${id}`),
{
ready: !!id,
onSuccess: (data) => {
form.setFieldsValue(data);
},
}
);
// Update product
const { run: updateProduct, loading: updating } = useRequest(
(values: Product) => apiPut(`/products/${id}`, values),
{
manual: true,
onSuccess: () => {
message.success('Product updated successfully');
},
}
);
return (
<Form form={form} onFinish={updateProduct}>
{/* Form fields */}
</Form>
);
};
Dynamic Forms
Dynamic Fields
import { Form, Button, Space } from 'antd';
import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
<Form.List name="items">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Form.Item
{...restField}
name={[name, 'name']}
rules={[{ required: true, message: 'Missing name' }]}
>
<Input placeholder="Item name" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'quantity']}
rules={[{ required: true, message: 'Missing quantity' }]}
>
<InputNumber placeholder="Quantity" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Add item
</Button>
</Form.Item>
</>
)}
</Form.List>
Form Validation Messages
Custom Messages
<Form.Item
name="email"
label="Email"
rules={[
{
required: true,
message: 'Please enter your email address',
},
{
type: 'email',
message: 'Please enter a valid email address',
},
]}
>
<Input />
</Form.Item>
Internationalized Messages
import { useTranslation } from 'ez-console';
export const ProductForm: React.FC = () => {
const { t } = useTranslation();
return (
<Form>
<Form.Item
name="name"
label={t('product.name')}
rules={[
{ required: true, message: t('product.nameRequired') },
{ min: 3, message: t('product.nameMinLength') },
]}
>
<Input />
</Form.Item>
</Form>
);
};
Best Practices
1. Use Form Instance
// ✅ Good: Use form instance
const [form] = Form.useForm();
<Form form={form}>
// ❌ Bad: No form instance
<Form>
2. Validate on Submit
// ✅ Good: Validate on submit
<Form onFinish={handleSubmit}>
// ❌ Bad: Validate on change (can be annoying)
<Form onValuesChange={handleValidate}>
3. Clear Error Messages
// ✅ Good: Clear, specific messages
{ required: true, message: 'Name is required' }
// ❌ Bad: Generic messages
{ required: true, message: 'Error' }
4. Use TypeScript
// ✅ Good: Typed form values
interface ProductFormData {
name: string;
price: number;
}
const handleSubmit = (values: ProductFormData) => {
// Type-safe
};
Related Topics
- Built-in Components - Form components
- Internationalization - i18n for forms
- API Integration - Submitting forms
Need help? Ask in GitHub Discussions.