@juantroconisf/lib
v8.0.0
Published
A form validation library for HeroUI.
Readme
@juantroconisf/lib
A powerful, type-safe form management and validation library optimized for HeroUI (formerly NextUI) and React.
Designed for complex applications, it provides O(1) value updates, stable ID-based array management, and deep nesting support, all while fully integrating with Yup for schema validation.
Table of Contents
Features
- 🎯 Polymorphic
onAPI: Single consistent interface forinput,select, andautocomplete. - 🧩 Deep Nesting: Effortlessly manage complex objects with dot-notation support (e.g.,
address.city). - 🔢 ID-Based Arrays: Stable state management for dynamic lists. Items are tracked by unique identifiers (default:
id), preventing state loss during reordering or deletions. - ⚡ O(1) Performance: Internal mapping for array items ensures lightning-fast updates regardless of list size.
- 🛡️ Total Type Safety: Best-in-class Intellisense for paths, types, and array identifiers.
- 🎨 HeroUI Optimized: Returns props ready to be spread directly onto HeroUI components (
isInvalid,errorMessage,onFieldBlur, etc.). - ✅ Yup Integration: Built-in support for Yup schemas for validation.
Installation
pnpm add @juantroconisf/lib yupQuick Start
Here is a complete example demonstrating scalar fields, object arrays, and validation.
import { useForm } from "@juantroconisf/lib";
import { string, number, array, object } from "yup";
import { Input, Button } from "@heroui/react";
const MyForm = () => {
const { on, state, helpers, onSubmit } = useForm({
name: string().required("Name is required").default(""),
items: array()
.of(
object().shape({
id: number().required(),
label: string().required("Label is required"),
}),
)
.default([{ id: 1, label: "Initial Item" }]),
});
const handleSubmit = (data) => {
console.log("Submitted:", data);
};
return (
<form onSubmit={onSubmit(handleSubmit)} className='flex flex-col gap-4'>
{/* 1. Scalar Registration */}
<Input {...on.input("name")} label='Name' />
{/* 2. Array Registration */}
{state.items.map((item) => (
<div key={item.id} className='flex gap-2 items-center'>
{/*
Bind by Path + ID
This ensures that even if the array order changes,
the input remains bound to the correct item.
*/}
<Input
{...on.input("items.label", item.id)}
label='Item Label'
/>
<Button
color="danger"
onClick={() => helpers.removeById("items", item.id)}
>
Remove
</Button>
</div>
))}
<div className="flex gap-2">
<Button
onClick={() => helpers.addItem("items", { id: Date.now(), label: "" })}
>
Add Item
</Button>
<Button type='submit' color="primary">Submit</Button>
</div>
</form>
);
};Usage Guide
Scalar Fields
Scalar fields are the bread and butter of any form. Use on.input, on.select, or on.autocomplete to bind them.
<Input {...on.input("fullName")} label="Full Name" />
<Input {...on.input("email")} type="email" label="Email" />Nested Objects
No special configuration needed. Just use standard dot notation.
const { on } = useForm({
settings: object({
theme: object({
mode: string().default("dark"),
}),
}),
});
<Input {...on.input("settings.theme.mode")} />Managing Arrays
The library shines with arrays. It tracks items by ID, so you can reorder or delete items without losing focus or state.
1. Binding Inputs:
Always bind using the composite syntax: path.field + itemId.
// ✅ Correct: Binds to the item with ID 123
on.input("users.name", 123)
// ❌ Avoid (unless primitive array): Binds to index 0
on.input("users", 0) 2. Manipulating the Array:
Use the helpers object.
// Add
helpers.addItem("users", { id: "new-id", name: "" });
// Remove by ID (Safe)
helpers.removeById("users", "user-id-123");
// Move (Reorder)
helpers.moveById("users", "from-id", "to-id");Validation
Validation is "Schema-First" via Yup.
- Define Rules: Pass a Yup schema to
useForm. - Auto-Validation:
- Fields validate on
blur. - If a field is
touchedandinvalid, it re-validates on every keystroke (onChange).
- Fields validate on
- Submit Validation: Use
onSubmitwrapper to validate all fields before executing your handler.
Submitting Forms
The onSubmit wrapper handles preventDefault, runs full validation, and only executes your callback if valid.
const { onSubmit } = useForm(...);
const handleSubmit = (data) => {
// 1. Validation has already passed!
// 2. Proceed with submission
api.post("/users", data);
};
return <form onSubmit={onSubmit(handleSubmit)}>...</form>;API Reference
useForm
const {
state,
on,
helpers,
metadata,
reset,
onSubmit,
reset,
onSubmit,
isDirty,
onFieldChange,
onFieldBlur
} = useForm(schema, options?);Arguments
| Argument | Type | Description |
| :--- | :--- | :--- |
| schema | Yup.Schema | A Yup schema object defining structure, default values, and validation rules. |
| options | FormOptions | Configuration object. |
Options
arrayIdentifiers: Mapping of array paths to their unique ID property (default:"id").
The on Object
The on object generates props for your components: value, onValueChange, isInvalid, errorMessage, onBlur.
| Method | Signature | Description |
| :--- | :--- | :--- |
| on.input | (path, [id]) | Generic input handler. Returns standard input props. |
| on.select | (path, [id]) | Returns props compatible with Select (handles selectedKeys). |
| on.autocomplete | (path, [id]) | Returns props compatible with Autocomplete (handles selectedKey). |
Array Helpers
Utilities for manipulating array state.
| Method | Description |
| :--- | :--- |
| addItem(key, item, index?) | Adds an item to the array. |
| removeItem(key, index) | Removes an item at a specific index. |
| removeById(key, id) | Recommended. Removes an item by its unique ID. |
| updateItem(key, index, val) | Replaces an item at a specific index. |
| moveItem(key, from, to) | Moves an item from one index to another. |
| moveById(key, fromId, toId) | Moves an item by ID. |
| getItem(key, id) | Retrieval helper. |
Metadata & Reset
| Property | Type | Description |
| :--- | :--- | :--- |
| metadata | Map<string, FieldMetadata> | Contains validation state (isInvalid, errorMessage) and isTouched. |
| reset | (options?) => void | Resets form state and metadata to initial values. |
Reset Options:
// Reset everything
reset();
// Keep specific fields
reset({ keys: ["organizationId"] });
// Only clear touched status (keep values)
reset({ onlyTouched: true });License
ISC © Juan T
