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

@quanticjs/react-forms

v8.0.0

Published

Form hooks for QuanticJS — Zod validation, automatic server error mapping from ProblemDetails

Readme

@quanticjs/react-forms

Form hook with Zod client-side validation and automatic server error mapping from QuanticJS ProblemDetails responses.

Install

pnpm add @quanticjs/react-core @quanticjs/react-forms zod

useForm

import { useForm } from '@quanticjs/react-forms';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email'),
});

function CreateUserForm() {
  const { register, errors, isSubmitting, handleSubmit } = useForm({
    schema,
    defaultValues: { name: '', email: '' },
    onSubmit: async (data) => {
      await client.post('/users', data);
    },
  });

  return (
    <form onSubmit={handleSubmit}>
      <input {...register('name')} />
      {errors.name && <span>{errors.name}</span>}

      <input {...register('email')} />
      {errors.email && <span>{errors.email}</span>}

      <button type="submit" disabled={isSubmitting}>
        Create
      </button>
    </form>
  );
}

How Validation Works

  1. Client-side first — Zod schema validates on submit. If invalid, field errors are set immediately (no network request).
  2. Server call — If Zod passes, onSubmit is called with the validated data.
  3. Server error mapping — If onSubmit throws an ApiError with isValidation: true, field errors from error.fieldErrors are automatically mapped to the form. No manual error handling needed.
  4. Non-validation errors — If the server throws a non-validation error (404, 500, etc.), it re-throws so your error boundary or mutation handler can catch it.

Config

interface UseFormConfig<TSchema extends ZodType> {
  schema: TSchema;                                    // Zod schema
  defaultValues?: Partial<Infer<TSchema>>;            // Initial field values
  onSubmit: (data: Infer<TSchema>) => Promise<void>;  // Called with validated data
}

Return Value

interface UseFormReturn<TSchema extends ZodType> {
  // State
  values: Partial<Infer<TSchema>>;     // Current form values
  errors: Record<string, string>;       // Field → first error message
  touched: Record<string, boolean>;     // Fields the user has interacted with
  isSubmitting: boolean;                // True during onSubmit execution
  isDirty: boolean;                     // True if any value changed from defaults

  // Field binding
  register(name: string): FieldProps;            // Spread onto <input>, <textarea>, <select>
  registerCheckbox(name: string): CheckboxFieldProps; // Spread onto boolean controls (checkbox, switch)

  // Manual control
  setValue(name: string, value: unknown): void;  // Set a field value programmatically
  setError(name: string, message: string): void; // Set a field error manually
  clearErrors(): void;                            // Clear all errors
  reset(values?: Partial<...>): void;             // Reset to defaults or new values
  handleSubmit(e?: FormEvent): Promise<void>;     // Validate + submit
}

register(name)

Returns props to spread onto form elements:

interface FieldProps {
  name: string;
  value: string;
  onChange: (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;
  onBlur: () => void;
}
  • onChange updates the value and clears the field's error
  • onBlur marks the field as touched

registerCheckbox(name)

register reads e.target.value (a string), so boolean controls need registerCheckbox, which reads e.target.checked:

interface CheckboxFieldProps {
  name: string;
  checked: boolean;
  onChange: (e: ChangeEvent<HTMLInputElement>) => void;  // reads e.target.checked
  onBlur: () => void;
}
<Checkbox {...form.registerCheckbox('agree')} />
<Switch {...form.registerCheckbox('notifications')} />

The existing register signature is unchanged.

setValue(name, value)

For non-standard inputs (date pickers, rich text, toggles):

const form = useForm({ schema, onSubmit });

<DatePicker
  value={form.values.date}
  onChange={(date) => form.setValue('date', date)}
/>

Error Display Patterns

Show errors only after touch — fieldError(form, name)

The canonical gate: returns the field's message only once the field is touched.

import { fieldError } from '@quanticjs/react-forms';

{fieldError(form, 'email') && (
  <span className="text-destructive text-sm">{fieldError(form, 'email')}</span>
)}

It is designed to feed FormField from @quanticjs/react-ui directly — label, description, error, and all ARIA wiring come for free:

import { FormField, Input } from '@quanticjs/react-ui';

<FormField label="Email" error={fieldError(form, 'email')}>
  <Input type="email" {...form.register('email')} />
</FormField>

(The packages stay decoupled: react-forms has no runtime dependency on react-ui — the integration is purely compositional.)

Show all errors on submit

All fields with errors are automatically marked as touched when validation fails — Zod errors and server-mapped errors alike — so fieldError (and the manual pattern above) shows them immediately after submit, while untouched clean fields never flash errors during typing.

Server errors appear on the right field

If the server returns:

{
  "type": "https://quantic.dev/errors/VALIDATION_ERROR",
  "errors": [
    { "field": "email", "message": "Email already exists" }
  ]
}

The email field error will show "Email already exists" — no manual mapping needed.

Root-level errors

Server errors without a field are stored under _root. They are not FormField's job — render them via the existing QueryErrorPanel from @quanticjs/react-ui or an inline alert:

{errors._root && (
  <div role="alert" className="text-destructive">{errors._root}</div>
)}

Exports

useForm, fieldError
type UseFormConfig, type UseFormReturn, type FieldProps, type CheckboxFieldProps