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 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-hook-form-component

v0.2.0

Published

A headless React form library for managing complex state, validation, and error handling

Readme

🚀 react-hook-form-component

Type-safe, design-system-agnostic building blocks for React Hook Form. Compose complex, dynamic forms with first-class TypeScript types, conditional visibility/disable rules, and UI-library independence (Ant Design, Material UI, Chakra, Tailwind, or your own components).

✨ Why react-hook-form-component?

  • Built on top of react-hook-form: minimal re-renders, great performance, familiar API.
  • 🎨 Design system agnostic: bring any input component from Antd, MUI, Chakra, or custom.
  • 📝 TypeScript-first: strong typing for values, names, and component props.
  • 🔄 Dynamic forms: add/remove field-array items and auto-prefix nested names.
  • 🎯 Conditional logic: hide or disable fields based on other field values.
  • 🧩 Headless primitives: render-prop or element cloning — you choose the control pattern.

📦 Installation

npm install react-hook-form-component
# or
yarn add react-hook-form-component

🎯 Quick Start

import { RHForm } from 'react-hook-form-component';
import { Input } from 'antd'; // or MUI/TextField, Chakra/Input, your custom input

type Values = {
  email: string;
  newsletter: boolean;
  reason?: string;
};

export default function Example() {
  return (
    <RHForm<Values>
      showAsterisk
      formOptions={{ defaultValues: { email: '', newsletter: false, reason: '' } }}
      onChange={(values) => console.log(values)}
    >
      <RHForm.Control<Values, 'email'> name='email' label='Email' rules={{ required: 'Required' }}>
        <Input placeholder='[email protected]' />
      </RHForm.Control>

      <RHForm.Control<Values, 'newsletter'> name='newsletter' label='Subscribe?'>
        {/* any checkbox component works */}
        <input type='checkbox' />
      </RHForm.Control>

      {/* Conditionally show based on newsletter */}
      <RHForm.Control<Values, 'reason'>
        name='reason'
        label='Reason'
        visibility={{
          operator: 'AND',
          conditions: [{ name: 'newsletter', value: true, operator: 'EQUALS' }],
        }}
      >
        <Input placeholder='Tell us more' />
      </RHForm.Control>
    </RHForm>
  );
}

🎨 Works With Any UI Library

Use Ant Design, Material UI, Chakra, Mantine, Headless UI, Tailwind, or custom inputs. RHForm.Control wires up value/blur/change/disabled props automatically when you pass a React element, or you can use a render function to get full control over field, fieldState, and formState.

📌 Element pattern (clone element)

<RHForm.Control name='firstName' label='First name' rules={{ required: 'Required' }}>
  <Input />
  {/* MUI: <TextField /> / Chakra: <Input /> / custom <MyInput /> */}
  {/* The control receives id, name, value, onChange, onBlur, disabled automatically */}
  {/* Errors render below the control by default */}
  {/* Asterisk appears for required fields when showAsterisk is true */}
  {/* Label and optional description integrate above the control */}
</RHForm.Control>

🔧 Render function pattern

<RHForm.Control name='age' rules={{ min: { value: 18, message: 'Adults only' } }}>
  {({ field, fieldState }) => <input type='number' {...field} aria-invalid={!!fieldState.error} />}
  {/* Full control of rendering; manage props, classes, and aria yourself */}
</RHForm.Control>

🎭 Conditional Visibility and Disable

Hide or disable a field based on other fields using strongly-typed conditions.

<RHForm.Control
  name='coupon'
  label='Coupon Code'
  visibility={{
    operator: 'AND',
    conditions: [
      { name: 'newsletter', value: true, operator: 'EQUALS' },
      { name: 'email', operator: 'INCLUDES', value: '@' },
    ],
  }}
  disabled={{
    operator: 'OR',
    conditions: [{ name: 'email', operator: 'NOT_INCLUDES', value: '@' }],
  }}
>
  <input />
  {/* If visibility evaluates false, the whole control is hidden. If disabled evaluates true, the control is disabled. */}
</RHForm.Control>

Supported Operators

Type-aware operators for conditional logic:

| Type | Operators | | ------- | -------------------------------------------------------------------------------------------------- | | String | EQUALS, NOT_EQUALS, STARTS_WITH, ENDS_WITH, INCLUDES, NOT_INCLUDES | | Number | EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL | | Boolean | EQUALS, NOT_EQUALS |

📋 Dynamic Arrays with RHForm.ControlArray

Easily build repeatable sections (field arrays). Child control names are auto-prefixed (e.g., items.0.name). Any visibility/disabled conditions on children are also auto-prefixed to match the correct nested paths.

type Values = { items: { name: string; price: number }[] };

<RHForm<Values> formOptions={{ defaultValues: { items: [{ name: '', price: 0 }] } }}>
  <RHForm.ControlArray name='items'>
    {/* children can contain multiple nested RHForm.Control elements */}
    <div>
      <RHForm.Control name='name' label='Item name'>
        <input />
      </RHForm.Control>
      <RHForm.Control name='price' label='Price'>
        <input type='number' />
      </RHForm.Control>
    </div>
  </RHForm.ControlArray>
</RHForm>;

📚 API Reference

RHForm (component)

| Prop | Type | Default | Description | | -------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | children | ReactNode | — | Form content. Use RHForm.Control and RHForm.ControlArray inside. | | style | React.CSSProperties | {} | Container style wrapper. | | className | string | "" | Container class name. | | formOptions | UseFormProps<TFieldValues> | { mode: 'onSubmit', reValidateMode: 'onChange', shouldFocusError: true } plus your overrides | Passed to useForm. | | showAsterisk | boolean | false | Adds a red * to labels of required fields (when rules exist). | | ref | React.Ref<RHFormRef<TFieldValues>> | — | Imperative ref exposing a safe subset of UseFormReturn (e.g., setValue, trigger, formState, etc.). | | onChange | (values: DeepPartial<TFieldValues>) => void | undefined | Called on any watched value change. |

RHFormRef<TFieldValues> exposes: setValue, setError, clearErrors, getValues, reset, setFocus, resetField, trigger, unregister, watch, handleSubmit, formState, submit.

RHForm.Control (field wrapper)

| Prop | Type | Default | Description | | ------------------ | ------------------------------------------------------------------------ | -------------- | ---------------------------------------------------------------------------------------------------------------- | | children | ReactElement \| (ctx) => ReactElement | — | Either pass an input element to be auto-wired, or a render function receiving { field, fieldState, formState } | | name | FieldPath<TFieldValues> | — | Field path (fully typed by TFieldValues) | | rules | RegisterOptions<TFieldValues, TName> | undefined | Validation rules (RHF) | | className | string | "" | Wrapper class | | label | string | undefined | Optional label | | id | string | Auto-generated | Control id. Auto-generated if omitted; prefixed inside arrays | | value | any | undefined | External default value for render-function mode | | shouldUnregister | boolean | undefined | Unregister on unmount (RHF) | | disabled | boolean \| Visibility<TFieldValues> | false | Disable based on a boolean or a condition set | | visibility | boolean \| Visibility<TFieldValues> | true | Show/hide based on a boolean or a condition set | | description | string \| { text: string; position?: 'LABEL_RIGHT' \| 'LABEL_BOTTOM' } | undefined | Optional helper text; displays to the right of the label or below it |

RHForm.ControlArray (repeatable groups)

Extends UseFieldArrayProps (except control) with:

| Prop | Type | Default | Description | | ------------ | ---------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------- | | ref | React.Ref<FormControlArrayRef<TFieldValues, TArrayName>> | — | Imperative access to underlying useFieldArray API | | className | string | undefined | Wrapper class | | children | ReactNode | — | Nested structure including RHForm.Control components; names are auto-prefixed per item | | visibility | ControlArrayVisibilityMap<TFieldValues, TArrayName> | undefined | Map of child field visibilities, auto-prefixed per row | | disabled | ControlArrayVisibilityMap<TFieldValues, TArrayName> | undefined | Map of child field disabilities, auto-prefixed per row |

Visibility<TFieldValues> shape

type Visibility<TFieldValues> = {
  operator: 'AND' | 'OR';
  conditions: Array<{
    name: FieldPath<TFieldValues>;
    value: string | number | boolean;
    operator: /* operator matches inferred type of the field */
  }>;
};

🏗️ Design Principles

  • 🎯 Headless first: all UI is opt-in. Bring your own styles and components.
  • 🔗 Minimal glue: thin wrappers around RHF controllers and arrays.
  • ⚙️ Ergonomic defaults: id auto-generation, label/description helpers, required asterisk.
  • 📐 Predictable typing: generic parameters keep field names and values aligned.

📄 License

MIT © Albin Britto