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

  1. Form manages submission and collects values
  2. name is required on each field
  3. defaultValues to pre-fill (editing)
  4. required for mandatory fields
  5. Zod for advanced validation

Previous Chapter | Back to Module | Next Chapter: Display ->