React Hook Form alternative - TypeScript-first useForm hook with enterprise-grade state management. Schema-agnostic validation (Zod, Yup, Valibot), minimal re-renders, advanced form controls.
npm install el-form-react-hooksšŖ React Hook Form alternative - TypeScript-first useForm hook with enterprise-grade state management
Perfect React Hook Form alternative for developers who want to build custom UIs with full control over styling and components.
Why choose el-form-react-hooks over React Hook Form:
- ā
Better TypeScript integration - Full type inference with any validation library
- ā
Schema-agnostic validation - Works with Zod, Yup, Valibot, or custom functions
- ā
Modern state management - Optimized for React 18+ with better performance
- ā
Smaller bundle - 11KB vs React Hook Form's 25KB+ with dependencies
| Package | Use When | Bundle Size | Dependencies |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------- | ------------ |
| el-form-react-hooks | You want full control over UI/styling ā You are here | 11KB | None |
| el-form-react-components | You want pre-built components with Tailwind | 18KB | Tailwind CSS |
| el-form-react | You want both hooks + components | 29KB | Tailwind CSS |
| el-form-core | Framework-agnostic validation only | 4KB | None |
> ⨠Want instant beautiful forms? Try el-form-react-components for zero-boilerplate AutoForm that generates complete forms from schemas.
``bash`
npm install el-form-react-hooksor
yarn add el-form-react-hooksor
pnpm add el-form-react-hooks
- useForm - Main form management hook
- TypeScript types - Full type safety
- 11KB bundle size - Lightweight, no UI dependencies
- Zero styling dependencies - Build any UI you want
`tsx
import { useForm } from "el-form-react-hooks";
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
age: z.number().min(18, "Must be 18 or older"),
});
function MyForm() {
const { register, handleSubmit, formState } = useForm({
schema: userSchema,
initialValues: { name: "", email: "", age: 18 },
});
return (
š”ļø Error Handling
Comprehensive error management with manual control:
$3
`tsx
const { setError, clearErrors, getFieldState, formState } = useForm({ schema });// Set field-specific errors
setError("email", "This email is already taken");
// Clear errors
clearErrors("email"); // Clear specific field
clearErrors(); // Clear all fields
// Check field state
const emailState = getFieldState("email");
console.log("Email error:", emailState.error);
console.log("Email touched:", emailState.isTouched);
`$3
`tsx
const handleSubmit = async (data) => {
try {
const response = await fetch("/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}); if (!response.ok) {
const errorData = await response.json();
// Handle field-specific errors
if (errorData.fieldErrors) {
Object.entries(errorData.fieldErrors).forEach(([field, message]) => {
setError(field, message);
});
}
// Handle general errors
if (errorData.message) {
setError("root", errorData.message);
}
return;
}
console.log("Success!");
} catch (error) {
setError("root", "Network error. Please try again.");
}
};
`$3
`tsx
const { register, watch, setError, clearErrors, trigger } = useForm({ schema });
const email = watch("email");// Debounced validation
useEffect(() => {
if (!email) return;
const timeoutId = setTimeout(async () => {
// First check schema validation
const isSchemaValid = await trigger("email");
if (!isSchemaValid) return;
// Then check external validation
try {
const response = await fetch(
/api/validate-email?email=${email});
const data = await response.json(); if (data.taken) {
setError("email", "Email already registered");
} else {
clearErrors("email");
}
} catch (error) {
console.warn("Email validation failed:", error);
}
}, 500);
return () => clearTimeout(timeoutId);
}, [email, setError, clearErrors, trigger]);
`$3
`tsx
const {
register,
handleSubmit,
formState,
setError,
clearErrors,
getFieldState,
setFocus,
} = useForm({ schema });const onSubmit = handleSubmit(
async (data) => {
try {
await submitForm(data);
} catch (error) {
// Set errors and focus first error field
if (error.fieldErrors) {
Object.entries(error.fieldErrors).forEach(([field, message]) => {
setError(field, message);
});
setFocus(Object.keys(error.fieldErrors)[0]);
}
}
},
(errors) => {
// Handle validation errors
console.log("Validation failed:", errors);
setFocus(Object.keys(errors)[0]);
}
);
`š API Reference
$3
#### Options
-
schema - Zod schema for validation
- initialValues - Initial form values (optional)
- validateOnChange - Validate on input change (default: true)
- validateOnBlur - Validate on input blur (default: true)#### Returns
-
register(fieldName) - Register field with form
- handleSubmit(onValid, onError) - Form submission handler
- formState - Current form state (values, errors, touched, etc.)
- setValue(path, value) - Set field value programmatically
- addArrayItem(path, item) - Add item to array field
- removeArrayItem(path, index) - Remove item from array field
- reset() - Reset form to initial statešļø Package Structure
This is part of the el-form ecosystem:
-
el-form-core - Framework-agnostic validation logic (4KB)
- el-form-react-hooks - React hooks only (11KB) ā You are here
- el-form-react-components - Pre-built UI components (18KB)
- el-form-react - Everything combined (29KB)šØ Need Pre-built Components?
If you want ready-to-use form components with Tailwind styling:
`bash
npm install el-form-react-components
``tsx
import { AutoForm } from "el-form-react-components"; console.log(data)} />;
``- Documentation
- GitHub
- npm
MIT