jlnstack

React Hook Form

Type-safe react-hook-form integration with Standard Schema

React Hook Form

Type-safe react-hook-form integration using Standard Schema.

Installation

npm install @jlnstack/form react-hook-form @hookform/resolvers

Usage

Create a Form

import { createForm } from "@jlnstack/form/react-hook-form";
import { z } from "zod";

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

const { useForm, useFormContext } = createForm(schema);

useForm

Returns a typed react-hook-form instance with the resolver pre-configured.

function MyForm() {
  const form = useForm({
    defaultValues: {
      name: "",
      email: "",
    },
  });

  return (
    <form onSubmit={form.handleSubmit((data) => {
      // data is typed as { name: string; email: string }
      console.log(data);
    })}>
      <input {...form.register("name")} />
      {form.formState.errors.name && (
        <span>{form.formState.errors.name.message}</span>
      )}

      <input {...form.register("email")} />
      {form.formState.errors.email && (
        <span>{form.formState.errors.email.message}</span>
      )}

      <button type="submit">Submit</button>
    </form>
  );
}

useFormContext

Access form state from nested components. Use with react-hook-form's FormProvider.

import { FormProvider } from "react-hook-form";

function MyForm() {
  const form = useForm();

  return (
    <FormProvider {...form}>
      <form onSubmit={form.handleSubmit(console.log)}>
        <NameField />
        <EmailField />
        <button type="submit">Submit</button>
      </form>
    </FormProvider>
  );
}

function NameField() {
  const { register, formState } = useFormContext();

  return (
    <div>
      <input {...register("name")} />
      {formState.errors.name && <span>{formState.errors.name.message}</span>}
    </div>
  );
}

Other Schema Libraries

Works with any Standard Schema compliant library:

Valibot

import { createForm } from "@jlnstack/form/react-hook-form";
import * as v from "valibot";

const schema = v.object({
  username: v.pipe(v.string(), v.minLength(3)),
  password: v.pipe(v.string(), v.minLength(8)),
});

const { useForm } = createForm(schema);

ArkType

import { createForm } from "@jlnstack/form/react-hook-form";
import { type } from "arktype";

const schema = type({
  username: "string>2",
  password: "string>7",
});

const { useForm } = createForm(schema);

Transform Types

When your schema transforms input to a different output type, the types are correctly inferred:

const schema = z.object({
  age: z.string().transform((val) => parseInt(val, 10)),
});

const { useForm } = createForm(schema);

function AgeForm() {
  const form = useForm();

  return (
    <form onSubmit={form.handleSubmit((data) => {
      // data.age is number (transformed)
      console.log(data.age + 1);
    })}>
      {/* Input accepts string */}
      <input {...form.register("age")} />
    </form>
  );
}

On this page