Auto-Generate Forms from Zod Schemas – react-rhf-zod-form

Description:

react-rhf-zod-form is a React library that generates forms automatically from Zod schemas. It connects zod definitions directly to react-hook-form logic to render type-safe inputs without manual markup.

It infers field types from the schema structure, maps them to registered UI components, and handles validation states through the React Hook Form resolver.

This library removes the need to manually wire register, handleSubmit, and error states for every field. It detects Zod types like z.string(), z.number(), or z.date() and renders the corresponding input elements.

You maintain control over the UI by registering your own component library once at the application root. It works well for developers who want to build type-safe forms quickly while keeping full control over the visual layer.

Features

  • Automatic Type Detection: It scans Zod schemas to select the correct input type, such as mapping z.string().email() to an email input.
  • Schema Refinements: The validation logic supports refine() and superRefine() for complex cross-field checks like password confirmation.
  • Component Registry: You define the visual components (inputs, labels, buttons) globally, so the form generator uses your existing UI kit.
  • Translation Support: It accepts translation functions from libraries like i18next or next-intl to localize labels and error messages.
  • Layout Control: The children pattern accepts custom JSX to arrange fields into grids or specific structures when the default vertical stack is insufficient.
  • TypeScript Inference: It derives all prop types directly from the Zod schema to prevent type mismatches.

Preview

generate-forms-zod-schemas

Use Cases

  • Admin Dashboards: Generate repetitive CRUD forms for backend resources without writing unique JSX for every entity.
  • Rapid Prototyping: Spin up functional forms with validation immediately after defining the data model.
  • Complex Validation: Implement forms that require interdependent field validation, such as date ranges or matching passwords, using standard Zod methods.
  • Multi-step Wizards: Manage form state across multiple steps by passing partial schemas to individual form instances.

How to Use It

Installation

Install the main package along with the required peer dependencies.

npm install @snowpact/react-rhf-zod-form react-hook-form zod @hookform/resolvers

Global Setup

You must configure the library once at your application’s entry point (e.g., App.tsx, main.tsx, or a dedicated setup file). This step registers your UI components so the generator knows how to render specific field types.

import { setupSnowForm } from '@snowpact/react-rhf-zod-form';
import type { RegisteredComponentProps, FormUILabelProps } from '@snowpact/react-rhf-zod-form';
// 1. Define your custom input component
function MyInput({ value, onChange, placeholder, disabled, className, name }: RegisteredComponentProps<string>) {
  return (
    <input
      id={name}
      name={name}
      type="text"
      value={value ?? ''}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
      disabled={disabled}
      className={`border p-2 rounded ${className ?? ''}`}
    />
  );
}
// 2. Define your label component
function MyLabel({ children, required, invalid, htmlFor }: FormUILabelProps) {
  return (
    <label htmlFor={htmlFor} className={`block mb-1 ${invalid ? 'text-red-500' : ''}`}>
      {children}
      {required && <span className="text-red-500 ml-1">*</span>}
    </label>
  );
}
// 3. Register everything
setupSnowForm({
  translate: (key) => key, // Pass your translation function here
  components: {
    text: MyInput,
    email: (props) => <MyInput {...props} type="email" />,
    password: (props) => <MyInput {...props} type="password" />,
    number: (props) => <MyInput {...props} type="number" />,
    checkbox: ({ value, onChange }) => (
      <input type="checkbox" checked={!!value} onChange={(e) => onChange(e.target.checked)} />
    ),
    // Add other types: textarea, select, date, etc.
  },
  formUI: {
    label: MyLabel,
    description: ({ children }) => <p className="text-sm text-gray-500">{children}</p>,
    errorMessage: ({ message }) => <p className="text-sm text-red-500">{message}</p>,
  },
  submitButton: ({ loading, disabled, children }) => (
    <button type="submit" disabled={disabled || loading} className="bg-blue-500 text-white px-4 py-2 rounded">
      {loading ? 'Sending...' : children}
    </button>
  ),
  styles: {
    form: 'space-y-4',
    formItem: 'flex flex-col',
  },
});

Basic Usage

Pass a Zod schema to the SnowForm component. The library handles the rendering and validation automatically.

import { SnowForm } from '@snowpact/react-rhf-zod-form';
import { z } from 'zod';
const userSchema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
  age: z.number().min(18),
});
export function UserForm() {
  return (
    <SnowForm
      schema={userSchema}
      onSubmit={async (values) => {
        console.log('Form Data:', values);
        // API calls go here
      }}
      onSuccess={() => alert('Success!')}
    />
  );
}

Field Overrides

You can modify specific fields without changing the schema. Use the overrides prop to change input types, labels, or placeholders.

<SnowForm
  schema={userSchema}
  overrides={{
    username: {
      label: 'Display Name',
      placeholder: 'Enter your public name',
    },
    email: {
      description: 'We will never share your email.',
    },
    age: {
      // Force a specific component type if auto-detection misses
      type: 'number', 
      emptyAsNull: true, // Converts empty strings to null
    },
  }}
/>

Custom Rendering

For fields that require unique UI logic not covered by the global registry, use the render function within overrides.

<SnowForm
  schema={userSchema}
  overrides={{
    avatar: {
      label: 'Profile Picture',
      render: ({ value, onChange, error }) => (
        <div className="custom-uploader">
          <img src={value || '/default-avatar.png'} alt="Preview" />
          <button type="button" onClick={() => onChange('/new-image.png')}>
            Upload
          </button>
          {error && <span className="error">{error}</span>}
        </div>
      ),
    },
  }}
/>

Layout Control (Children Pattern)

The default behavior stacks fields vertically. Use the children render function to create custom layouts, such as grids or grouped sections.

<SnowForm schema={userSchema} onSubmit={handleSubmit}>
  {({ renderField, renderSubmitButton }) => (
    <div className="grid grid-cols-2 gap-4">
      <div className="col-span-2">
        {renderField('username')}
      </div>
      {renderField('email')}
      {renderField('age')}
      <div className="col-span-2 mt-4">
        {renderSubmitButton({ children: 'Register Now' })}
      </div>
    </div>
  )}
</SnowForm>

Integration with Translation Libraries

You can connect localization libraries like next-intl or i18next during setup.

Example with next-intl:

import { useTranslations } from 'next-intl';
import { setupSnowForm } from '@snowpact/react-rhf-zod-form';
import { useEffect } from 'react';
function FormProvider({ children }) {
  const t = useTranslations('Forms');
  useEffect(() => {
    setupSnowForm({
      translate: t,
      components: { /* ... registered components ... */ },
    });
  }, [t]);
  return children;
}

API Reference

SnowForm Props

PropTypeDescription
schemaZodSchemaThe Zod schema that defines the form structure and validation.
overridesobjectConfiguration object to customize labels, types, and rendering for specific fields.
defaultValuesobjectInitial values for the form fields.
onSubmitfunctionAsync handler called when validation passes. Receives form data.
onSuccessfunctionCallback executed after onSubmit resolves successfully.
onSubmitErrorfunctionCallback executed if onSubmit throws an error.
childrenfunctionRender prop for custom layout control. Receives renderField and form helpers.
classNamestringCSS class applied to the <form> element.

Setup Options (setupSnowForm)

OptionDescription
componentsA map of input types (text, email, select, etc.) to React components.
formUIComponents for labels, descriptions, and error messages.
submitButtonComponent for the submit button, receiving loading and disabled states.
translateFunction to translate keys into localized strings.
stylesGlobal CSS classes for the form container and field wrappers.

Related Resources

  • React Hook Form: The underlying library that handles form state and validation performance.
  • Zod: The schema declaration and validation library used to define data shapes.
  • Shadcn UI: A collection of re-usable components that pairs well with the custom component registry.

FAQs:

Q: Can I use my own UI library like Material UI or Ant Design?
A: Yes. You register your library’s components in the setupSnowForm function. The library delegates rendering to whatever components you provide.

Q: How does it handle complex validation like “Confirm Password”?
A: It supports Zod’s refine and superRefine methods. You define the validation logic in the schema, and the form displays the error on the relevant field automatically.

Q: Is it possible to hide specific fields?
A: Yes. You can use the hidden type in the overrides configuration or omit the field from the layout when using the children pattern.

Q: Does it support server-side validation errors?
A: You can handle server errors in the onSubmit function and set them manually on the form using the form object provided by the children render prop or onSubmitError.

Add Comment