Chapter 3: Forms
Form
The Form component manages form submission and validation.
Basic Syntax
import { Form, Input, Button } from '@cap-rel/smartcommon';
function ContactForm() {
const handleSubmit = (values) => {
console.log(values);
// { name: 'John', email: 'john@example.com' }
};
return (
<Form onSubmit={handleSubmit}>
<Input name="name" label="Name" required />
<Input name="email" label="Email" type="email" required />
<Button type="submit">Submit</Button>
</Form>
);
}
With Initial Values
function EditProductForm({ product }) {
const handleSubmit = async (values) => {
await api.private.put(`products/${product.id}`, { json: values });
};
return (
<Form
onSubmit={handleSubmit}
defaultValues={{
label: product.label,
price: product.price,
description: product.description
}}
>
<Input name="label" label="Product Name" />
<Input name="price" label="Price" type="number" />
<Input name="description" label="Description" />
<Button type="submit">Save</Button>
</Form>
);
}
Input
Versatile text input field.
Main Props
| Prop | Type | Description |
|---|---|---|
| name | string | Field name (required) |
| label | string | Display label |
| type | string | 'text', 'email', 'password', 'number', 'tel' |
| placeholder | string | Placeholder text |
| required | boolean | Required field |
| disabled | boolean | Disabled field |
| error | string | Error message |
Examples
// Simple text
<Input name="firstName" label="First Name" />
// Email with validation
<Input name="email" label="Email" type="email" required />
// Password
<Input name="password" label="Password" type="password" />
// Number
<Input name="quantity" label="Quantity" type="number" min={0} max={100} />
// Phone
<Input name="phone" label="Phone" type="tel" />
// With placeholder
<Input
name="search"
placeholder="Search..."
type="text"
/>
// Disabled
<Input name="ref" label="Reference" disabled value="PRD-001" />
Textarea
Multiline text area.
import { Textarea } from '@cap-rel/smartcommon';
<Textarea
name="description"
label="Description"
rows={4}
placeholder="Describe the product..."
/>
Select
Dropdown list.
import { Select } from '@cap-rel/smartcommon';
// Simple options
<Select
name="category"
label="Category"
options={[
{ value: 'electronics', label: 'Electronics' },
{ value: 'clothing', label: 'Clothing' },
{ value: 'food', label: 'Food' }
]}
/>
// With empty value
<Select
name="status"
label="Status"
placeholder="Select..."
options={[
{ value: 'draft', label: 'Draft' },
{ value: 'published', label: 'Published' },
{ value: 'archived', label: 'Archived' }
]}
/>
// Options from API
function ProductForm() {
const [categories, setCategories] = useState([]);
useEffect(() => {
api.private.get('categories').json().then(data => {
setCategories(data.map(c => ({
value: c.id,
label: c.name
})));
});
}, []);
return (
<Form>
<Select
name="category_id"
label="Category"
options={categories}
/>
</Form>
);
}
Boolean
On/off toggle switch.
import { Boolean } from '@cap-rel/smartcommon';
<Boolean
name="isActive"
label="Active Product"
/>
<Boolean
name="notifications"
label="Receive notifications"
defaultValue={true}
/>
Checker
Checkbox.
import { Checker } from '@cap-rel/smartcommon';
<Checker
name="acceptTerms"
label="I accept the terms"
required
/>
// Group of checkboxes
<div>
<Checker name="options.express" label="Express delivery" />
<Checker name="options.gift" label="Gift wrapping" />
<Checker name="options.insurance" label="Insurance" />
</div>
Calendar
Date picker.
import { Calendar } from '@cap-rel/smartcommon';
// Simple date
<Calendar
name="birthdate"
label="Birth Date"
/>
// With constraints
<Calendar
name="startDate"
label="Start Date"
minDate={new Date()}
maxDate={new Date(2025, 11, 31)}
/>
// Date and time
<Calendar
name="appointment"
label="Appointment"
showTime={true}
/>
Timer
Time picker.
import { Timer } from '@cap-rel/smartcommon';
<Timer
name="startTime"
label="Start Time"
/>
<Timer
name="duration"
label="Duration"
format="HH:mm"
/>
Range
Value slider.
import { Range } from '@cap-rel/smartcommon';
<Range
name="price"
label="Maximum Price"
min={0}
max={1000}
step={10}
/>
<Range
name="rating"
label="Minimum Rating"
min={1}
max={5}
step={0.5}
/>
ColorPicker
Color picker.
import { ColorPicker } from '@cap-rel/smartcommon';
<ColorPicker
name="color"
label="Product Color"
/>
FilesUploader
File upload.
import { FilesUploader } from '@cap-rel/smartcommon';
<FilesUploader
name="documents"
label="Documents"
accept=".pdf,.doc,.docx"
multiple
maxFiles={5}
/>
PhotosUploader
Photo upload with preview.
import { PhotosUploader } from '@cap-rel/smartcommon';
<PhotosUploader
name="photos"
label="Product Photos"
maxFiles={10}
maxSize={5 * 1024 * 1024} // 5 MB
/>
SignaturePad
Handwritten signature area.
import { SignaturePad } from '@cap-rel/smartcommon';
<SignaturePad
name="signature"
label="Signature"
width={400}
height={200}
/>
Complete Example: Product Form
import { useEffect } from 'react';
import {
Page,
Block,
Form,
Input,
Textarea,
Select,
Boolean,
Calendar,
PhotosUploader,
Button
} from '@cap-rel/smartcommon';
import { useApi, useNavigation, useStates } from '@cap-rel/smartcommon';
export const ProductFormPage = ({ productId }) => {
const api = useApi();
const nav = useNavigation();
const isEdit = !!productId;
const st = useStates({
initialStates: {
product: null,
categories: [],
loading: isEdit,
submitting: false
}
});
// Load categories
useEffect(() => {
api.private.get('categories').json().then(data => {
st.set('categories', data.map(c => ({
value: c.id,
label: c.name
})));
});
}, []);
// Load product if editing
useEffect(() => {
if (isEdit) {
api.private.get(`products/${productId}`).json().then(data => {
st.set('product', data);
st.set('loading', false);
});
}
}, [productId]);
const handleSubmit = async (values) => {
st.set('submitting', true);
try {
if (isEdit) {
await api.private.put(`products/${productId}`, { json: values });
} else {
await api.private.post('products', { json: values });
}
nav.navigate('/products');
} catch (err) {
alert('Error: ' + err.message);
} finally {
st.set('submitting', false);
}
};
if (st.get('loading')) {
return <Page title="Loading..."><Spinner /></Page>;
}
return (
<Page title={isEdit ? 'Edit Product' : 'New Product'}>
<Block>
<Form
onSubmit={handleSubmit}
defaultValues={st.get('product') || {
label: '',
description: '',
price: 0,
category_id: null,
isActive: true,
availableFrom: null,
photos: []
}}
>
{/* Basic Information */}
<Input
name="label"
label="Product Name"
required
/>
<Textarea
name="description"
label="Description"
rows={4}
/>
<Input
name="price"
label="Price (€)"
type="number"
min={0}
step={0.01}
required
/>
<Select
name="category_id"
label="Category"
options={st.get('categories')}
required
/>
{/* Options */}
<Boolean
name="isActive"
label="Active Product"
/>
<Calendar
name="availableFrom"
label="Available From"
/>
{/* Photos */}
<PhotosUploader
name="photos"
label="Photos"
maxFiles={5}
/>
{/* Actions */}
<div className="flex gap-2 mt-4">
<Button
type="button"
variant="outline"
onClick={() => nav.navigate(-1)}
>
Cancel
</Button>
<Button
type="submit"
loading={st.get('submitting')}
>
{isEdit ? 'Save' : 'Create'}
</Button>
</div>
</Form>
</Block>
</Page>
);
};
Manual Validation with useForm
import { Form, Input, Button } from '@cap-rel/smartcommon';
import { useForm } from '@cap-rel/smartcommon';
function RegisterForm() {
const form = useForm({
defaultValues: { email: '', password: '', confirmPassword: '' }
});
const handleSubmit = async (data) => {
console.log(data);
};
// Manual validation via setField
const validateEmail = (value) => {
form.setField({
name: 'email',
value,
errors: {
required: { condition: !value },
format: { condition: value && !value.includes('@') }
}
});
};
return (
<Form form={form} onSubmit={handleSubmit}>
<Input name="email" label="Email" type="email" />
<Input name="password" label="Password" type="password" />
<Input name="confirmPassword" label="Confirm" type="password" />
<Button type="submit">Register</Button>
</Form>
);
}
Key Points to Remember
- Form manages submission and collects values
- name is required on each field
- defaultValues to pre-fill (editing)
- required for mandatory fields
- Zod for advanced validation
Previous Chapter | Back to Module | Next Chapter: Display ->