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

use-standard-schema

v0.4.3

Published

A React hook for managing form state using any standard schema compliant validation library.

Readme

useStandardSchema

A React hook for managing form state using any Standard Schema-compliant validator.

License NPM Version


Table of contents


Overview

useStandardSchema wraps a Standard Schema-compliant form definition (e.g. Zod, Valibot, ArkType, etc.) into a React hook for form handling. It streamlines validation, state, error handling, and submission with type safety via the Standard Schema interface.

Why useStandardSchema?

  • Works with any validator that implements the Standard Schema spec
  • Provides consistent form APIs regardless of validation library
  • Built with TypeScript support, ensuring type-safe validation and form usage
  • Integrates easily into React workflows
  • Supports nested objects with dot notation (e.g. "address.street1")

Prerequisites


Installation

npm install use-standard-schema
# or
yarn add use-standard-schema
# or
pnpm add use-standard-schema

Usage

Define your form once with defineForm, then consume it inside a component with useStandardSchema.

import { defineForm, useStandardSchema, type TypeFromDefinition } from "use-standard-schema"
import * as z from "zod"

const subscriptionForm = defineForm({
  email: {
    label: "Email",
    validate: z.email("Enter a valid email address"),
    defaultValue: "",  // optional
    description: "We'll send occasional updates.", // optional
  },
})

function onSubmitHandler (values: TypeFromDefinition<typeof subscriptionForm>) {
    console.log("Submitted:", values)
}

export function SubscriptionPage() {

  const { getForm, getField } = useStandardSchema(subscriptionForm)
  const formHandlers = getForm(onSubmitHandler)
  const email = getField("email")

  return (
    <form {...formHandlers}>
        
      <label htmlFor={email.name}>{email.label}</label>
      <input
        id={email.name}
        name={email.name}
        defaultValue={email.defaultValue}
        // value={defaultValue}
        aria-describedby={email.describedById}
        aria-errormessage={email.errorId}
      />
      <p id={email.describedById}>{email.description}</p>
      <p id={email.errorId} role="alert">{email.error}</p>

      <button type="submit">Subscribe</button>
    </form>
  )
}
  • getForm(onSubmit): Returns event handlers for the <form>. onSubmit only runs when valid.
  • getField(name): Returns the given field's metadata.

Examples

Additional examples are available.

Nested object fields

Nested objects are supported.

import { defineForm } from "use-standard-schema"
import * as z from "zod"

const addressForm = defineForm({
  address: {
    street1: { label: "Street", validate: z.string().min(2) },
  },
})

Error handling

useStandardSchema returns the getErrors method that returns all of the current validations errors. This can be useful for giving all form error messages in one location. NOTE: This is in addition to the getField method which returns the errors for a given field.

import type { ErrorEntry } from "use-standard-schema"

const { getErrors } = useStandardSchema(loginForm)
const errors = getErrors()

{errors.length > 0 && (
  <div role="alert">
    {errors.map(({ name, label, error }: ErrorEntry) => (
      <p key={name}>{label}: {error}</p>
    ))}
  </div>
)}

Touched and dirty helpers

Use the isTouched and isDirty helper methods to check whether or not the form, or a given field, has been modified or focused by the user.

  const { isTouched, isDirty } = useStandardSchema(addressForm)

  const isStreetTouched = isTouched("address.street1")
  const isStreetDirty = isDirty("address.street1")

  const isFormTouched = isTouched()
  const isFormDirty = isDirty()

Valid keys

A FormDefinition's key is an intersection between a valid JSON key and an HTML name attribute.


const formDefinition = defineForm({
    prefix: z.string(),                // valid
    "first-name": z.string(),          // valid
    "middle_name": z.string(),         // valid
    "last:name": z.string(),           // valid
    "street address": z.string()       // invalid
})

API

useStandardSchema returns a helpers for wiring form elements, reading state, and issuing manual updates.

useStandardSchema(formDefinition)

Passing a form definition using defineForm and pass the definition to the hook. The return value exposes the rest of the helpers documented below.

const { getForm, getField, getErrors, setField, setError, resetForm, isTouched, isDirty, watchValues } =
  useStandardSchema(myFormDefinition)

getForm(onSubmit)

Returns the event handlers for the <form>. It validates all fields and only invokes your handler when everything passes.

const form = getForm((values) => console.log(values))

return <form {...form}>...</form>

getField(name)

Returns metadata for a specific field for wiring inputs, labels, and helper text.

const email = getField("email")

<input
  name={email.name}
  defaultValue={email.defaultValue}
  aria-describedby={email.describedById}
  aria-errormessage={email.errorId}
  aria-invalid={!!email.error}
/>
<span id={email.describedById}>Enter your email address</span>
<span id={email.errorId}>{email.error}</span>

getErrors(name?)

Returns structured error data of type ErrorEntry for the whole form or for one specific field - perfect for summary banners or toast notifications.

const allErrors = getErrors()
const emailErrors = getErrors("email")

resetForm()

Clears errors, touched/dirty flags, and restores the original defaults. Note: The hook calls this automatically after a successful submit.

<button type="reset" onClick={resetForm}>Reset<button>

isTouched(name?) and isDirty(name?)

Report whether a field - or any field when called without arguments - has been interacted with or changed.

const hasEditedAnything = isDirty()
const isEmailTouched = isTouched("email")

watchValues(targets?, callback)

Subscribe to canonical form values without forcing extra React renders. The callback executes whenever any watched key changes and receives an object scoped to those fields.

Parameters

  • targets (optional): a single field name or array of field names. Omit to observe every value in the form.
  • callback(values): invoked with the latest values for the watched fields.

Returns

  • unsubscribe(): stop listening inside useEffect cleanups or teardown handlers.
const postToPreview = ({ plan, seats }) => {
    previewChannel.postMessage({ 
        quote: calculateQuote(plan, Number(seats)) 
    })
}

useEffect(() => {

  const unsubscribe = watchValues(["plan", "seats"], postToPreview)
  return unsubscribe

}, [watchValues])

toFormData(data)

Helper that converts a values object into a browser FormData instance for interoperability with fetch/XHR uploads.

const formData = toFormData(values)

setField(name, value)

Updates a field's value (for dependent fields, custom widgets, or multi-step wizards) and re-validates it. NOTE: You do not need to call this manually in most situations. It will occur automatically.

setField("address.postalCode", nextPostalCode)

setError(name, error)

Sets a manual error message for any field (for dependent fields, custom widgets, or multi-step wizards). Pass null or undefined to clear it. NOTE: You do not need to call this manually in most situations. It will occur automatically.

setError("email", new Error("Email already registered"))

Feedback & Support

If you encounter issues or have feature requests, open an issue on GitHub.


Changelog

  • v0.4.3
    • Fixed documentation issues.
    • Fixed missing ErrorEntry export.
  • v0.4.2
    • Added watchValues for monitoring value changes without rerender.
    • Fixed issue with ErrorInfo not being exported.
    • Field updates are safer, validation errors fall back to helpful defaults, and async checks no longer overwrite newer input.
    • Added a shadcn/ui Field example
    • Added additional tests to keep real-world flows covered.
  • v0.4.1 - Minor code fixes and documentation updates
  • v0.4.0 - Improved form state synchronization, renamed the FieldDefinitionProps type to FieldData, and ensured programmatic updates stay validated while tracking touched/dirty status.
  • View the full changelog for earlier releases.

License

MIT License