Quick Start
Build a simple contact form in 5 minutes.
Step 1: Create Field Componentsβ
ReFormer uses your own components to render form fields.
Basic Componentsβ
src/components/ui/Input.tsx
import * as React from 'react';
interface InputProps {
value?: string;
onChange?: (value: string) => void;
onBlur?: () => void;
placeholder?: string;
disabled?: boolean;
}
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ value, onChange, onBlur, placeholder, disabled }, ref) => (
<input
ref={ref}
type="text"
value={value ?? ''}
onChange={(e) => onChange?.(e.target.value)}
onBlur={onBlur}
placeholder={placeholder}
disabled={disabled}
className="border rounded px-3 py-2 w-full"
/>
)
);
src/components/ui/Textarea.tsx
import * as React from 'react';
interface TextareaProps {
value?: string;
onChange?: (value: string) => void;
onBlur?: () => void;
placeholder?: string;
disabled?: boolean;
}
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ value, onChange, onBlur, placeholder, disabled }, ref) => (
<textarea
ref={ref}
value={value ?? ''}
onChange={(e) => onChange?.(e.target.value)}
onBlur={onBlur}
placeholder={placeholder}
disabled={disabled}
rows={4}
className="border rounded px-3 py-2 w-full"
/>
)
);
Universal FormField Componentβ
src/components/ui/FormField.tsx
import * as React from 'react';
import { useFormControl, type FieldNode } from '@reformer/core';
interface FormFieldProps {
control: FieldNode<any>;
className?: string;
}
export const FormField: React.FC<FormFieldProps> = ({ control, className }) => {
const { value, errors, disabled, shouldShowError, componentProps } = useFormControl(control);
const Component = control.component;
return (
<div className={className}>
{componentProps.label && (
<label className="block mb-1 text-sm font-medium">{componentProps.label}</label>
)}
<Component
{...componentProps}
value={value ?? ''}
onChange={(e: unknown) => {
const newValue = (e as { target?: { value?: unknown } })?.target?.value ?? e;
control.setValue(newValue);
}}
onBlur={() => control.markAsTouched()}
disabled={disabled}
/>
{shouldShowError && (
<span className="text-red-500 text-sm mt-1 block">{errors[0]?.message}</span>
)}
</div>
);
};
Why FormField?
FormField automatically:
- Renders label from
componentProps - Binds value to form state
- Calls
markAsTouched()on blur - Shows validation errors
Step 2: Define Form Interfaceβ
type ContactFormType = {
name: string;
email: string;
message: string;
};
Step 3: Create Form Definitionβ
src/forms/contact-form.ts
import { createForm } from '@reformer/core';
import { required, email, minLength } from '@reformer/core/validators';
import { Input } from '@/components/ui/Input';
import { Textarea } from '@/components/ui/Textarea';
type ContactFormType = {
name: string;
email: string;
message: string;
};
export const createContactForm = () =>
createForm<ContactFormType>({
form: {
name: { value: '', component: Input, componentProps: { label: 'Name' } },
email: { value: '', component: Input, componentProps: { label: 'Email' } },
message: { value: '', component: Textarea, componentProps: { label: 'Message' } },
},
validation: (path) => {
required(path.name);
minLength(path.name, 2);
required(path.email);
email(path.email);
required(path.message);
minLength(path.message, 10);
},
});
Key Points
createForm<T>β factory function with automatic typingcomponentβ React component to render the fieldcomponentPropsβ props passed to the component (label, placeholder, etc.)validationβ declarative validation schema
Step 4: Create Form Componentβ
src/components/ContactForm.tsx
import { useMemo } from 'react';
import { createContactForm } from '@/forms/contact-form';
import { FormField } from '@/components/ui/FormField';
export function ContactForm() {
const form = useMemo(() => createContactForm(), []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
form.markAsTouched();
await form.validate();
if (form.valid.value) {
console.log('Submit:', form.value.value);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-md">
<FormField control={form.name} />
<FormField control={form.email} />
<FormField control={form.message} />
<button
type="submit"
disabled={form.invalid.value}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
Submit
</button>
</form>
);
}
Resultβ
You've created a form with:
- β TypeScript type safety
- β Declarative validation
- β Automatic error display
- β Clean and minimal code
Next Stepsβ
- Core Concepts β Learn about Nodes in depth
- Validation β All built-in validators
- Behaviors β Computed fields and conditional logic