Forms and Client Validation
Relevant source files
The following files were used as context for generating this wiki page:
The system utilizes a robust client-side form management and validation architecture built on @tanstack/react-form and Zod. This approach ensures type safety, consistency across different administrative modules (Users, Contracts, Customers, and Leave Types), and tight integration between the UI components and backend expectations.
Sources: packages/web/CLAUDE.md:11-13, packages/web/package.json:34
Core Architecture
The form architecture is centralized around the TanStack Form hook, which manages state, validation, and submission logic. Validation is handled through Zod schemas that are shared between the client forms and the server-side functions to maintain a single source of truth for data integrity.
Data Flow and Validation Lifecycle
The following diagram illustrates the flow from user input through validation to final submission:
The application enforces a policy where ad-hoc inline validator functions are prohibited in favor of formal Zod schemas passed to the form's validator adapter.
Sources: packages/web/CLAUDE.md:13-15, packages/web/src/routes/_authed/admin/contracts/-components/form.tsx:32-34
Component Structure
Forms are built using a standardized set of UI components, primarily sourced from shadcn/ui. A standard field implementation consists of a <Label>, an input-like component (such as <Input>, <Select>, or <TextField>), and a specialized <FieldError /> component.
Key Components
| Component | Description | Source |
|---|---|---|
TextField | A wrapper that handles internal binding to the TanStack field state. | packages/web/src/routes/_authed/admin/contracts/-components/form.tsx:12 |
FieldError | Renders validation error messages only after a field has been touched. | REVIEW.md:58-65 |
useForm | The primary hook for initializing form state and defining submission handlers. | packages/web/src/routes/_authed/admin/contracts/-components/form.tsx:30 |
form.Subscribe | Used to listen to form-wide state changes (e.g., canSubmit, isSubmitting). | packages/web/src/routes/_authed/admin/users/-components/form.tsx:254-256 |
The FieldError Component
To prevent UI drift and code duplication, error rendering is extracted into a standalone component. It joins multiple error messages into a comma-separated string and applies the text-destructive color.
function FieldError({ field }: { field: { state: { meta: { isTouched: boolean; errors: Array<unknown> } } } }) {
if (!field.state.meta.isTouched || field.state.meta.errors.length === 0) return null;
const message = field.state.meta.errors
.map((err) => (typeof err === 'string' ? err : (err as { message?: string })?.message))
.filter(Boolean)
.join(', ');
if (!message) return null;
return <p className="text-sm text-destructive">{message}</p>;
}
Sources: REVIEW.md:58-65, packages/web/CLAUDE.md:15-18
Validation Patterns
The system implements specific validation rules for different entities, which are strictly enforced via Zod.
Customer Validation
The customerSchema defines the following constraints:
- Name: Required and cannot be whitespace-only.
- Country: Required.
- Email: Must follow a valid email format if provided, but accepts empty strings for optional contact info.
Sources: packages/web/src/features/customers/customers.schemas.spec.ts:18-47
Complex Array Validation
For complex entities like Contracts or Users, forms utilize the mode="array" field state. This is seen in the Contract tasks list and User leave allowances, where nested fields are validated against child schemas.
Integration with Server Functions
Server functions created with createServerFn utilize the same Zod schemas for input validation. This ensures that the data sent to the backend is pre-validated on the client but also re-validated on the server before processing.
- Aligning Client/Server: Schemas are defined once in the feature directory (e.g.,
-schemas.ts) and imported by both the form component and the server function. - Input Validation: Every server function must pass a Zod schema to
.inputValidatorrather than a generic identity function.
Sources: packages/web/CLAUDE.md:21-25, packages/web/src/features/contracts/contracts.functions.ts:48-50
Summary
The "Forms and Client Validation" module provides a type-safe, declarative way to handle complex data entry. By leveraging @tanstack/react-form for state management and Zod for schema enforcement, the project maintains high data integrity while reducing boilerplate code through standardized components like FieldError and shared feature schemas.