Piplup
@piplup/rhf-core

Installation — @piplup/rhf-core

Install @piplup/rhf-core with React Hook Form, check the peer dependency versions, and set up a first form.

@piplup/rhf-core contains the building blocks for connecting your own input components to React Hook Form: the FormContainer component and adapter hooks such as useControllerAdapter. It has no UI of its own. The ready-made components for HTML, MUI and other libraries live in @piplup/rhf-adapters, which is built on this package.

Install

Install the package together with React Hook Form:

npm install @piplup/rhf-core react-hook-form

Peer dependencies

PackageVersion
react^19.0.0
react-dom^19.0.0
react-hook-form>=7.65.0 <8.0.0

React 18 is not supported by this version. @piplup/utils is a regular dependency and is installed for you.

Entry point

Everything is exported from the package root. The package ships ES module (.mjs) and CommonJS (.js) builds with TypeScript types.

import {
  FormContainer,
  FormErrorProvider,
  useControllerAdapter,
  useFieldState,
  useFieldStateAdapter,
  useFormReset,
  useFormSetFocus,
  useFormStateAdapter,
} from "@piplup/rhf-core";

The bundle starts with a "use client" directive, so its components are Client Components in the Next.js App Router. Function props such as onSubmit can't be passed from a Server Component, so put your forms in a file marked "use client".

Minimal setup

FormContainer creates the form with useForm, provides it through context and renders a <form noValidate>. Fields inside it find the form through context, so they don't need a control prop.

"use client";

import { FormContainer, useControllerAdapter } from "@piplup/rhf-core";

function TextInput({ name, label }: { name: string; label: string }) {
  const field = useControllerAdapter<string>({
    name,
    required: true,
    composeHelperText: true,
  });

  return (
    <label>
      {label}
      <input
        ref={field.ref}
        name={field.name}
        value={field.value}
        onChange={field.onChange}
        onBlur={field.onBlur}
        aria-invalid={field.error}
      />
      {field.error && <span role="alert">{field.helperText}</span>}
    </label>
  );
}

export default function SignupForm() {
  return (
    <FormContainer
      defaultValues={{ email: "" }}
      onSubmit={(values) => console.log(values)}
    >
      <TextInput name="email" label="Email" />
      <button type="submit">Sign up</button>
    </FormContainer>
  );
}

If you already create the form with useForm, pass the result as formContext instead of the useForm options. See FormContainer.

Next steps

On this page