npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

el-form-react-hooks

v3.10.0

Published

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.

Downloads

61

Readme

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

🧭 Choose the Right Package

| 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.

📦 Installation

npm install el-form-react-hooks
# or
yarn add el-form-react-hooks
# or
pnpm add el-form-react-hooks

🎯 What's Included

  • 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

🚀 Quick Start

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 (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input
        {...register("name")}
        placeholder="Name"
        className="my-custom-input"
      />
      {formState.errors.name && (
        <span className="error">{formState.errors.name}</span>
      )}

      <input
        {...register("email")}
        type="email"
        placeholder="Email"
        className="my-custom-input"
      />
      {formState.errors.email && (
        <span className="error">{formState.errors.email}</span>
      )}

      <input
        {...register("age")}
        type="number"
        placeholder="Age"
        className="my-custom-input"
      />
      {formState.errors.age && (
        <span className="error">{formState.errors.age}</span>
      )}

      <button type="submit" disabled={formState.isSubmitting}>
        Submit
      </button>
    </form>
  );
}

🛡️ Error Handling

Comprehensive error management with manual control:

Manual Error Control

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);

API Error Integration

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.");
  }
};

Real-time Validation

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]);

Advanced Error Handling

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

useForm(options)

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:

npm install el-form-react-components
import { AutoForm } from "el-form-react-components";

<AutoForm schema={userSchema} onSubmit={(data) => console.log(data)} />;

🔗 Links

📄 License

MIT