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

@nexus-state/form

v0.1.0

Published

Type-safe form management with field-level granularity for Nexus State

Readme

@nexus-state/form

Forms for complex applications — with DevTools, atomic architecture, and full type safety

npm version TypeScript React


🎯 When to Choose @nexus-state/form

✅ Perfect for:

| Scenario | Why | |----------|--------| | Forms with 50+ fields | Atomic architecture = minimal re-renders | | Complex validation | Async validation with debouncing, retry, cache out of the box | | Enterprise applications | DevTools for debugging, audit trail of changes | | Cross-framework projects | Single core for React, Vue, Svelte | | High-performance UIs | Granular updates, no unnecessary renders |

❌ Not suitable for:

  • Simple forms (login, contact) → use React Hook Form
  • Prototypes and MVPs → pick something lighter
  • Server-side forms (Next.js Server Actions) → trend is server validation

🚀 Quick Start

Installation

npm install @nexus-state/form @nexus-state/core @nexus-state/react

Basic Example (React)

import { useForm } from '@nexus-state/form/react';
import { required, email, minLength } from '@nexus-state/form';

interface RegisterForm {
  username: string;
  email: string;
  password: string;
}

function RegistrationForm() {
  const { register, handleSubmit, formState } = useForm<RegisterForm>({
    defaultValues: {
      username: '',
      email: '',
      password: '',
    },
    mode: 'onBlur',              // Validate on blur
    showErrorsOnTouched: true,   // Show errors after touch
  });

  const onSubmit = async (data) => {
    await fetch('/api/register', {
      method: 'POST',
      body: JSON.stringify(data),
    });
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('username', { validators: [required, minLength(3)] })}
        placeholder="Username"
      />
      {formState.errors.username && (
        <span className="error">{formState.errors.username}</span>
      )}

      <input
        {...register('email', { validators: [required, email] })}
        placeholder="Email"
      />
      {formState.errors.email && (
        <span className="error">{formState.errors.email}</span>
      )}

      <input
        type="password"
        {...register('password', { validators: [required, minLength(8)] })}
        placeholder="Password"
      />
      {formState.errors.password && (
        <span className="error">{formState.errors.password}</span>
      )}

      <button type="submit" disabled={!formState.isValid}>
        Register
      </button>
    </form>
  );
}

🔥 Key Features

1. DevTools Integration 🛠️

Debug forms in Redux DevTools — see every change, validation, stack trace:

import { createStore } from '@nexus-state/core';
import { devTools } from '@nexus-state/devtools';
import { useForm } from '@nexus-state/form/react';

const store = createStore();

// Apply DevTools plugin
const devtools = devTools({
  name: 'My App Forms',
  trace: true,          // Stack trace for every change
  maxAge: 100,          // Keep last 100 actions
});

devtools.apply(store);

// Now in Redux DevTools you can see:
// ✅ Every field change
// ✅ When validation triggered
// ✅ Validation errors
// ✅ Time-travel to previous states

Benefit: Debugging complex forms takes minutes instead of hours.


2. Async Validation Out of the Box 🌐

Uniqueness checks, API validation with debouncing, retry, cache:

import { createField } from '@nexus-state/form';
import { useField } from '@nexus-state/form/react';

function UsernameField() {
  const { field, fieldState } = useField('username', {
    initialValue: '',
    validators: [required],
    asyncValidators: [{
      validate: async (value) => {
        const response = await fetch(`/api/check-username?username=${value}`);
        const { available } = await response.json();
        return available ? null : 'Username already taken';
      },
      options: {
        debounce: 500,    // Wait 500ms after last keystroke
        cache: true,      // Cache results
        retry: 2,         // 2 retries on network error
        timeout: 5000,    // 5 second timeout
      }
    }],
    validateOn: 'onChange',
  });

  return (
    <div>
      <input {...field} />
      {fieldState.validating && <span>Checking...</span>}
      {fieldState.error && <span className="error">{fieldState.error}</span>}
    </div>
  );
}

Built-in:

  • ✅ Debouncing (don't spam API on every keystroke)
  • ✅ Caching (don't request same value twice)
  • ✅ Retry logic (recover from network errors)
  • ✅ Timeout (protect from slow responses)

3. Flexible Validation Triggers 🎯

Control when validation runs:

const form = useForm({
  mode: 'onBlur',           // First validation on blur
  reValidateMode: 'onChange', // After error — validate on every change
  showErrorsOnTouched: true,  // Show errors only after touch
});

// UX example:
// 1. User types — errors hidden
// 2. Loses focus — show errors
// 3. Starts fixing — validate on the fly (fast feedback)

Modes:

  • 'onChange' — validate on every change
  • 'onBlur' — validate on blur
  • 'onSubmit' — validate only on submit

4. Dynamic Field Arrays 📋

Add, remove, reorder fields:

import { useFieldArray } from '@nexus-state/form/react';

interface Skill { name: string; level: number }

function SkillsForm() {
  const { fields, append, remove, move } = useFieldArray<Skill>('skills', {
    defaultValue: [{ name: '', level: 1 }],
  });

  return (
    <div>
      {fields.map((skill, index) => (
        <div key={skill.id}>
          <input value={skill.name} onChange={...} />
          <button onClick={() => remove(index)}>Remove</button>
          <button onClick={() => move(index, index - 1)}>↑</button>
          <button onClick={() => move(index, index + 1)}>↓</button>
        </div>
      ))}
      <button onClick={() => append({ name: '', level: 1 })}>
        Add Skill
      </button>
    </div>
  );
}

Methods: append, prepend, remove, insert, swap, move, update, replace


5. Full TypeScript Support 🎯

Type inference out of the box:

interface User {
  name: string;
  age: number;
  email: string;
}

const { register, handleSubmit } = useForm<User>();

register('name');    // ✅ OK
register('age');     // ✅ OK
register('invalid'); // ❌ TypeScript error: Property 'invalid' does not exist

handleSubmit((data) => {
  // data is typed as User
  console.log(data.name);  // ✅
  console.log(data.age);   // ✅
});

📊 Comparison with Alternatives

| Feature | @nexus-state/form | React Hook Form | Formik | Redux Form | |---------|-------------------|-----------------|--------|------------| | DevTools | ✅ Redux DevTools | ❌ | ❌ | ✅ | | Async validation | ✅ Out of box | ⚠️ Manual | ⚠️ Manual | ⚠️ Manual | | Debouncing | ✅ Built-in | ❌ | ❌ | ❌ | | Retry logic | ✅ Built-in | ❌ | ❌ | ❌ | | Validation triggers | ✅ onChange/blur/submit | ⚠️ Limited | ⚠️ Limited | ⚠️ Limited | | Performance (50+ fields) | 🟢 Excellent | 🟢 Excellent | 🟡 Medium | 🔴 Slow | | Cross-framework | ✅ React/Vue/Svelte | ❌ React only | ❌ React only | ❌ React only | | Bundle size | 🟡 ~15KB | 🟢 ~7KB | 🟡 ~14KB | 🔴 ~25KB | | Learning curve | 🟡 Medium | 🟢 Low | 🟢 Low | 🔴 High |


🏗️ Architecture

Atomic Approach

Each field is a separate atom. Subscribe only to needed changes:

// Traditional approach (React Hook Form, Formik)
// Single state for entire form → re-render all fields when one changes

// @nexus-state/form
// Each field is separate atom → re-render only changed field

const usernameField = createField(store, 'username', { ... });
const emailField = createField(store, 'email', { ... });

// Changing username doesn't trigger re-render of email field

Result: Performance doesn't degrade on forms with 100+ fields.


📚 API Reference

useForm

const {
  register,           // Register field
  handleSubmit,       // Submit handler
  formState,          // Form state
  reset,              // Reset form
  setValue,           // Set field value
  getValue,           // Get field value
  setError,           // Set field error
  clearErrors,        // Clear errors
  trigger,            // Manual validation
} = useForm<FormValues>({
  defaultValues: { ... },
  mode: 'onBlur',
  reValidateMode: 'onChange',
  showErrorsOnTouched: true,
});

useField

const {
  field,              // Props for input
  fieldState,         // Field state
  helpers,            // Helpers
} = useField('name', {
  initialValue: '',
  validators: [required],
  asyncValidators: [...],
  validateOn: 'onBlur',
  showErrorsOnTouched: true,
});

useFieldArray

const {
  fields,             // Array of fields
  append, prepend,    // Add items
  remove, insert,     // Remove/insert
  swap, move,         // Reorder
  update, replace,    // Update
} = useFieldArray<Item>('items', {
  defaultValue: [],
});

🎯 Usage Examples

Enterprise Form (100+ fields)

import { useForm } from '@nexus-state/form/react';
import { devTools } from '@nexus-state/devtools';

// Enable DevTools for debugging
const store = createStore();
devTools({ trace: true, maxAge: 200 }).apply(store);

function LoanApplicationForm() {
  const { register, handleSubmit, formState } = useForm<LoanApplication>({
    mode: 'onBlur',
    showErrorsOnTouched: true,
  });

  // 100+ fields without performance degradation
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Personal info section */}
      <input {...register('firstName')} />
      <input {...register('lastName')} />
      {/* ... 98 more fields */}
    </form>
  );
}

Why @nexus-state/form:

  • DevTools for debugging complex logic
  • Performance on 100+ fields
  • Audit trail of changes

Form with Async Validation

function RegistrationForm() {
  const { register, handleSubmit } = useForm({
    defaultValues: { username: '', email: '' },
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('username', {
          validators: [required, minLength(3)],
          asyncValidators: [{
            validate: async (v) => {
              const res = await fetch(`/api/check?username=${v}`);
              return res.available ? null : 'Taken';
            },
            options: { debounce: 500, cache: true }
          }]
        })}
      />
      
      <input
        {...register('email', {
          validators: [required, email],
          asyncValidators: [{
            validate: async (v) => {
              const res = await fetch(`/api/check?email=${v}`);
              return res.available ? null : 'Registered';
            },
            options: { debounce: 500 }
          }]
        })}
      />
    </form>
  );
}

Why @nexus-state/form:

  • Built-in debouncing (don't spam API)
  • Caching (don't request same value twice)
  • Retry on network errors

Dynamic Form (Form Builder)

function FormBuilder() {
  const { fields, append, remove } = useFieldArray<FormField>('fields', {
    defaultValue: [],
  });

  return (
    <div>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input value={field.label} onChange={...} />
          <select value={field.type}>
            <option value="text">Text</option>
            <option value="number">Number</option>
          </select>
          <button onClick={() => remove(index)}>Remove</button>
        </div>
      ))}
      <button onClick={() => append({ label: '', type: 'text' })}>
        Add Field
      </button>
    </div>
  );
}

Why @nexus-state/form:

  • Simple array manipulation
  • Each field is separate atom
  • Easy to add validation

⚠️ When NOT to Use

Simple Forms

// ❌ Overkill for login form
const { register } = useForm({ ... });

// ✅ Better with React Hook Form
import { useForm } from 'react-hook-form';

Server-side Validation

// ❌ If all validation is on server (Next.js Server Actions)
// ✅ Use server actions + Zod schemas

Prototypes

// ❌ For 2-day MVP
// ✅ Pick something simpler (Formik, react-hook-form)

🔧 Integrations

Zod Schemas

import { z } from 'zod';
import { zodValidator } from '@nexus-state/form';

const schema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
});

const form = useForm({
  schema: zodValidator(schema),
});

Yup Schemas

import * as yup from 'yup';
import { yupValidator } from '@nexus-state/form';

const schema = yup.object({
  username: yup.string().min(3).required(),
  email: yup.string().email().required(),
});

const form = useForm({
  schema: yupValidator(schema),
});

📦 Bundle Size

| Package | Size (minified) | Gzip | |---------|-----------------|------| | Core | ~8KB | ~3KB | | React hooks | ~4KB | ~2KB | | Utils | ~3KB | ~1KB | | Total | ~15KB | ~6KB |


🤝 Contributing

Welcome:

  • ✅ Bug reports
  • ✅ Feature requests
  • ✅ Pull requests
  • ✅ Documentation

📄 License

MIT © Nexus State Contributors


🔗 Links