New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

react-z-form

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-z-form

Lightweight, high-performance React form library powered by Zustand. Zero re-renders, React 18 safe, TypeScript support. Alternative to Formik and React Hook Form.

latest
Source
npmnpm
Version
1.1.0
Version published
Weekly downloads
2
Maintainers
1
Weekly downloads
 
Created
Source

react-z-form

npm version npm downloads bundle size license

The fastest React form library — Lightweight, high-performance form state management powered by Zustand.

A modern alternative to Formik and React Hook Form. Zero re-renders, React 18 concurrent mode safe, TypeScript ready.

Why react-z-form?

Featurereact-z-formReact Hook FormFormik
Re-rendersMinimalModerateHigh
Bundle Size~3KB~9KB~13KB
React 18 SafeYesPartialNo
Field SubscriptionsYesYesNo
Zod/Yup SupportYesYesYup only
TypeScriptFullFullPartial
Learning CurveEasyMediumMedium

Features

  • Zero Re-renders — Fine-grained subscriptions mean only changed fields re-render
  • React 18 Ready — Built with useSyncExternalStore for concurrent mode
  • Tiny Bundle — ~3KB minified + gzipped
  • TypeScript First — Complete type definitions included
  • Zod Integration — Built-in schema validation adapter
  • No Dependencies on Redux — Powered by Zustand
  • Works with Any UI — MUI, Chakra UI, Ant Design, Tailwind, or custom components

Installation

npm install react-z-form zustand immer

or

yarn add react-z-form zustand immer

or

pnpm add react-z-form zustand immer

Quick Start

import { FormProvider, Field, useForm } from "react-z-form";

function LoginForm() {
  return (
    <FormProvider form="login" initialValues={{ email: "", password: "" }}>
      <FormContent />
    </FormProvider>
  );
}

function FormContent() {
  const { submitWith, isSubmitting, isValid } = useForm();

  const handleSubmit = async (values) => {
    await api.login(values);
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); submitWith(handleSubmit); }}>
      <Field name="email" validate={(v) => (!v ? "Email is required" : undefined)}>
        {({ input, meta }) => (
          <div>
            <input {...input} type="email" placeholder="Email" />
            {meta.touched && meta.error && <span>{meta.error}</span>}
          </div>
        )}
      </Field>

      <Field name="password" validate={(v) => (!v ? "Password is required" : undefined)}>
        {({ input, meta }) => (
          <div>
            <input {...input} type="password" placeholder="Password" />
            {meta.touched && meta.error && <span>{meta.error}</span>}
          </div>
        )}
      </Field>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Logging in..." : "Login"}
      </button>
    </form>
  );
}

API Reference

Components

FormProvider

Wraps your form and provides context to all child components.

<FormProvider
  form="myForm"
  initialValues={{ name: "", email: "" }}
  keepOnUnmount={false}
>
  {children}
</FormProvider>
PropTypeRequiredDescription
formstringYesUnique form identifier
initialValuesobjectNoInitial form values
keepOnUnmountbooleanNoKeep form state after unmount

Field

Render prop component for individual form fields.

<Field
  name="email"
  validate={(value) => !value ? "Required" : undefined}
  validateOn="blur"
>
  {({ input, meta }) => (
    <>
      <input {...input} />
      {meta.touched && meta.error && <span>{meta.error}</span>}
    </>
  )}
</Field>
PropTypeRequiredDescription
namestringYesField name
initialValueanyNoInitial value for this field
validatefunctionNo(value, allValues) => error | undefined
validateOn"change" | "blur" | "submit" | "all"NoWhen to validate (default: "all")

Render Props:

PropTypeDescription
input.valueanyCurrent field value
input.onChangefunctionChange handler
input.onBlurfunctionBlur handler
meta.touchedbooleanField has been blurred
meta.dirtybooleanValue differs from initial
meta.errorstring | undefinedValidation error

FormSpy

Watch the entire form state reactively.

<FormSpy>
  {(state) => (
    <pre>{JSON.stringify(state, null, 2)}</pre>
  )}
</FormSpy>

Hooks

useForm(formName?)

Main hook for form control and state.

const {
  // State
  values,
  errors,
  touched,
  dirty,
  isSubmitting,
  submitCount,

  // Computed (v1.1.0)
  isValid,
  isDirty,
  isTouched,

  // Actions
  reset,
  setErrors,
  setFieldValue,
  setFieldTouched,
  setValues,
  setFieldError,
  submitWith,
} = useForm("myForm");
PropertyTypeDescription
valuesobjectAll form values
errorsobjectAll validation errors
touchedobjectTouched state per field
dirtyobjectDirty state per field
isSubmittingbooleanForm is submitting
submitCountnumberNumber of submit attempts
isValidbooleanNo validation errors
isDirtybooleanAny field is dirty
isTouchedbooleanAny field is touched
MethodDescription
reset(values?)Reset form to initial or new values
setFieldValue(field, value)Set a single field value
setFieldTouched(field, touched?)Set field touched state
setValues(values)Set multiple values at once
setFieldError(field, error)Set a single field error
setErrors(errors)Set multiple errors at once
submitWith(callback)Handle async form submission

useField(name, formName?)

Hook version of the Field component.

const { input, meta } = useField("email");

return <input {...input} />;

useFormState(formName?)

Subscribe to the entire form state.

const { values, errors, isSubmitting } = useFormState("myForm");

Validation

Inline Validation

<Field
  name="email"
  validate={(value) => {
    if (!value) return "Email is required";
    if (!/\S+@\S+\.\S+/.test(value)) return "Invalid email format";
    return undefined;
  }}
>
  {({ input, meta }) => <input {...input} />}
</Field>

Zod Schema Validation

import { z } from "zod";
import { FormProvider, Field, useForm, zodField, zodForm } from "react-z-form";

// Field-level validation
const emailSchema = z.string().email("Invalid email address");

<Field name="email" validate={zodField(emailSchema)}>
  {({ input, meta }) => <input {...input} />}
</Field>

// Form-level validation on submit
const formSchema = z.object({
  email: z.string().email("Invalid email"),
  password: z.string().min(8, "Password must be at least 8 characters"),
});

function FormContent() {
  const { submitWith, setErrors, values } = useForm();

  const handleSubmit = async (values) => {
    const errors = zodForm(formSchema)(values);
    if (Object.keys(errors).length > 0) {
      setErrors(errors);
      return;
    }
    await api.submit(values);
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); submitWith(handleSubmit); }}>
      {/* fields */}
    </form>
  );
}

Validation Modes

Control when validation runs:

// Validate on blur only
<Field name="email" validate={validate} validateOn="blur">

// Validate on change only
<Field name="email" validate={validate} validateOn="change">

// Validate on submit only (manual)
<Field name="email" validate={validate} validateOn="submit">

// Validate on both change and blur (default)
<Field name="email" validate={validate} validateOn="all">

Examples

Login Form

import { FormProvider, Field, useForm } from "react-z-form";

function LoginForm() {
  return (
    <FormProvider form="login" initialValues={{ email: "", password: "" }}>
      <LoginFormContent />
    </FormProvider>
  );
}

function LoginFormContent() {
  const { submitWith, isSubmitting, isValid } = useForm();

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      submitWith(async (values) => {
        const response = await fetch("/api/login", {
          method: "POST",
          body: JSON.stringify(values),
        });
        if (!response.ok) throw new Error("Login failed");
      });
    }}>
      <Field name="email" validate={(v) => !v ? "Required" : undefined}>
        {({ input, meta }) => (
          <div>
            <label>Email</label>
            <input {...input} type="email" />
            {meta.touched && meta.error && <span className="error">{meta.error}</span>}
          </div>
        )}
      </Field>

      <Field name="password" validate={(v) => !v ? "Required" : undefined}>
        {({ input, meta }) => (
          <div>
            <label>Password</label>
            <input {...input} type="password" />
            {meta.touched && meta.error && <span className="error">{meta.error}</span>}
          </div>
        )}
      </Field>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Logging in..." : "Login"}
      </button>
    </form>
  );
}

Registration Form with Zod

import { z } from "zod";
import { FormProvider, Field, useForm, zodField } from "react-z-form";

const schema = {
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().email("Invalid email address"),
  password: z.string().min(8, "Password must be at least 8 characters"),
  confirmPassword: z.string(),
};

function RegistrationForm() {
  return (
    <FormProvider
      form="register"
      initialValues={{ name: "", email: "", password: "", confirmPassword: "" }}
    >
      <RegistrationFormContent />
    </FormProvider>
  );
}

function RegistrationFormContent() {
  const { submitWith, isSubmitting, values, setFieldError } = useForm();

  return (
    <form onSubmit={(e) => {
      e.preventDefault();

      // Cross-field validation
      if (values.password !== values.confirmPassword) {
        setFieldError("confirmPassword", "Passwords do not match");
        return;
      }

      submitWith(async (values) => {
        await fetch("/api/register", {
          method: "POST",
          body: JSON.stringify(values),
        });
      });
    }}>
      <Field name="name" validate={zodField(schema.name)}>
        {({ input, meta }) => (
          <div>
            <input {...input} placeholder="Full Name" />
            {meta.touched && meta.error && <span>{meta.error}</span>}
          </div>
        )}
      </Field>

      <Field name="email" validate={zodField(schema.email)}>
        {({ input, meta }) => (
          <div>
            <input {...input} type="email" placeholder="Email" />
            {meta.touched && meta.error && <span>{meta.error}</span>}
          </div>
        )}
      </Field>

      <Field name="password" validate={zodField(schema.password)}>
        {({ input, meta }) => (
          <div>
            <input {...input} type="password" placeholder="Password" />
            {meta.touched && meta.error && <span>{meta.error}</span>}
          </div>
        )}
      </Field>

      <Field name="confirmPassword">
        {({ input, meta }) => (
          <div>
            <input {...input} type="password" placeholder="Confirm Password" />
            {meta.touched && meta.error && <span>{meta.error}</span>}
          </div>
        )}
      </Field>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Creating account..." : "Register"}
      </button>
    </form>
  );
}

Programmatic Field Control

function DynamicForm() {
  const { setFieldValue, setValues, reset, values } = useForm("dynamic");

  return (
    <div>
      {/* Pre-fill from API */}
      <button onClick={() => setValues({
        name: "John Doe",
        email: "john@example.com"
      })}>
        Load User Data
      </button>

      {/* Update single field */}
      <button onClick={() => setFieldValue("status", "active")}>
        Set Active
      </button>

      {/* Reset form */}
      <button onClick={() => reset()}>
        Clear Form
      </button>

      {/* Reset with new values */}
      <button onClick={() => reset({ name: "", email: "", status: "pending" })}>
        Reset to Defaults
      </button>
    </div>
  );
}

With Material UI

import { TextField, Button, CircularProgress } from "@mui/material";
import { FormProvider, Field, useForm } from "react-z-form";

function MUIForm() {
  return (
    <FormProvider form="mui-form" initialValues={{ email: "" }}>
      <MUIFormContent />
    </FormProvider>
  );
}

function MUIFormContent() {
  const { submitWith, isSubmitting } = useForm();

  return (
    <form onSubmit={(e) => { e.preventDefault(); submitWith(console.log); }}>
      <Field name="email" validate={(v) => !v ? "Required" : undefined}>
        {({ input, meta }) => (
          <TextField
            {...input}
            label="Email"
            error={meta.touched && !!meta.error}
            helperText={meta.touched && meta.error}
            fullWidth
            margin="normal"
          />
        )}
      </Field>

      <Button
        type="submit"
        variant="contained"
        disabled={isSubmitting}
        startIcon={isSubmitting && <CircularProgress size={20} />}
      >
        Submit
      </Button>
    </form>
  );
}

TypeScript

Full TypeScript support with generics:

import { FormProvider, Field, useForm } from "react-z-form";

interface LoginFormValues {
  email: string;
  password: string;
}

function LoginForm() {
  const { values, submitWith } = useForm("login");

  // values is typed as Record<string, any>
  // For stronger typing, use your own interface
  const typedValues = values as LoginFormValues;

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      submitWith(async (vals) => {
        const typedVals = vals as LoginFormValues;
        await login(typedVals.email, typedVals.password);
      });
    }}>
      {/* ... */}
    </form>
  );
}

Migration from Other Libraries

From Formik

// Formik
<Formik initialValues={{ email: "" }} onSubmit={handleSubmit}>
  {({ values, handleChange, handleBlur }) => (
    <input name="email" value={values.email} onChange={handleChange} onBlur={handleBlur} />
  )}
</Formik>

// react-z-form
<FormProvider form="myForm" initialValues={{ email: "" }}>
  <Field name="email">
    {({ input }) => <input {...input} />}
  </Field>
</FormProvider>

From React Hook Form

// React Hook Form
const { register, handleSubmit } = useForm();
<input {...register("email")} />

// react-z-form
const { submitWith } = useForm();
<Field name="email">
  {({ input }) => <input {...input} />}
</Field>

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Requires React 18+

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a PR.

  • Fork the repository
  • Create your feature branch (git checkout -b feature/amazing-feature)
  • Commit your changes (git commit -m 'Add amazing feature')
  • Push to the branch (git push origin feature/amazing-feature)
  • Open a Pull Request

License

MIT License - see the LICENSE file for details.

Keywords: react form, react forms, form validation, react form library, zustand form, formik alternative, react hook form alternative, react 18 forms, typescript form, zod validation, form state management, react form builder, lightweight form library

Keywords

react

FAQs

Package last updated on 26 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts