@juantroconisf/lib
v11.11.0
Published
A form validation library for HeroUI.
Readme
@juantroconisf/lib
A type-safe form management library for React and HeroUI. It eliminates boilerplate through a declarative on.* API that bridges your Yup schema directly to HeroUI component props — complete with validation, error messages, dirty tracking, and ID-based array management.
Why this library?
Managing forms in React often involves manual state syncing and verbose validation logic. This library acts as a bridge, automatically mapping your Yup schema's constraints directly to your HeroUI components.
Key Benefits
- Zero Boilerplate: No more manual
value,onChange, andonBlurwiring. - Type-Safe: Automatic inference from your schema with full Intellisense.
- Auto-Validation: Errors and
isRequiredstates flow naturally from your schema. - ID-Based Arrays: Seamless handling of dynamic lists with $O(1)$ performance.
Table of Contents
Quick Start
Get up and running in under a minute.
1. Install
pnpm add @juantroconisf/lib yup2. Implementation
import { useForm } from "@juantroconisf/lib";
import { string, boolean } from "yup";
import { Input, Switch, Button } from "@heroui/react";
const MyForm = () => {
const { on, ControlledForm } = useForm({
fullName: string().required().default(""),
darkMode: boolean().default(false),
});
return (
<ControlledForm onSubmit={(data) => console.log(data)}>
{/* on.input() automatically handles value, onValueChange, isInvalid, and errorMessage */}
<Input {...on.input("fullName")} label='Full Name' />
<Switch {...on.switch("darkMode")}>Dark Mode</Switch>
<Button type='submit' color='primary'>
Submit
</Button>
</ControlledForm>
);
};How-to Guides
Practical recipes for common form scenarios.
1. Working with Nested Objects
Dot notation reaches arbitrarily deep. TypeScript validates that the path exists and has the correct type.
<Input {...on.input("settings.profile.username")} label="Username" />
<Switch {...on.switch("settings.notifications")}>
Enable Notifications
</Switch>2. Managing Dynamic Arrays
Arrays of objects are tracked by item id (or a custom identifier). This ensures inputs hold their state correctly even after re-ordering or deletions.
{state.users.map((user) => (
<div key={user.id}>
{/* Bind to a field inside the array item using: path + itemId */}
<Input {...on.input("users.name", user.id)} label='Name' />
<Button onPress={() => helpers.removeById("users", user.id)}>
Remove
</Button>
</div>
))}
<Button onPress={() => helpers.addItem("users", { id: Date.now(), name: "" })}>
Add User
</Button>3. N-Level Deep Structures
For arrays inside objects inside arrays, pass a variadic sequence alternating between structural paths and identifiers.
// Schema: items[].form_response.input_values[].value
<Input
{...on.input("items", itemId, "form_response.input_values", inputId, "value")}
label='Deep Value'
/>4. Performing Manual Updates
To update state outside of a component (e.g., in an API response handler), use the manual setters.
const { onFieldChange, onArrayItemChange } = useForm(schema);
// Update a top-level or nested field
onFieldChange("settings.theme", "dark");
// Update a field inside an array item
onArrayItemChange({ at: "users.name", id: userId, value: "Alice" });Reference
useForm(schema, options?)
| Option | Type | Description |
| :--- | :--- | :--- |
| arrayIdentifiers | Record<string, string> | Override the ID field (default: "id") |
| resetOnSubmit | boolean | Reset form after success (default: false) |
| keepValues | (keyof State)[] | Fields to exclude from reset |
Validation Results
The validateAll, validateItem, and the helpers validation methods return a ValidationResponse object:
// Top level
const { isValid, results } = await validateAll();
const { isValid, results } = await validateFields(["firstName", "lastName"]);
// Within helpers
const { isValid, results } = await helpers.validateItem("users", userId);isValid: boolean; errors: string[]; // List of composite IDs with errors results: ErrorResult[]; // Detailed error objects }
interface ErrorResult { id: string; // e.g. "users.0.name" label: string; // resolved from DOM (e.g. "User Name") or prettified ID message: string; // validation error message }
#### Automatic Label Capture
The library lazily resolves labels the first time a field is blurred or validated. It checks in priority:
1. `aria-labelledby` element's text.
2. Standard `<label for="...">` text.
3. Fallback: Prettified last segment of the ID (capitalized).
### The `on.*` API
Methods that bridge schema logic to HeroUI components.
| Method | HeroUI Component | Key Props Returned |
| :--- | :--- | :--- |
| `on.input()` | `Input`, `Textarea` | `value`, `onValueChange` |
| `on.select()` | `Select` | `selectedKeys`, `onSelectionChange` |
| `on.switch()` | `Switch` | `isSelected`, `onValueChange` |
| `on.checkbox()` | `Checkbox` | `isSelected`, `onValueChange` |
| `on.radio()` | `RadioGroup` | `value`, `onValueChange` |
### Array Helpers (`helpers.*`)
| Method | Description |
| :--- | :--- |
| `addItem(path, item)` | Append a new item |
| `removeById(path, id)` | Fast $O(1)$ removal by ID |
| `updateById(path, id, partial)` | Shallow merge by ID |
| `moveById(path, fromId, toId)` | Reorder items |
---
## Explanation
### The Bridge Architecture
Traditional form libraries often require you to manually map state to component props (`value={state.foo} onChange={...}`).
This library uses a **Bridge Pattern**:
1. Your **Schema** defines the "Source of Truth".
2. The **`on.*` methods** act as the bridge, translating that truth into the specific prop contract needed by each HeroUI component.
3. This ensures that validation errors, required indicators, and values are always perfectly in sync without manual wiring.
### Localization
Validation messages are automatically localized by reading the `LOCALE` cookie (`en` or `es`).
```ts
document.cookie = "LOCALE=es; path=/;";License
ISC © Juan T
